From daa3a27edb5f76572b2853aa645cb63b53217071 Mon Sep 17 00:00:00 2001 From: localhorst Date: Mon, 3 Aug 2020 22:40:07 +0200 Subject: [PATCH 01/41] stated to implement threading --- include/drive.h | 5 +++ include/reHDD.h | 12 ++++++ include/tui.h | 41 ++++++++++++++++++ include/tui_data.h | 45 ++++++++++++++++++++ makefile | 2 +- src/TUI/tui.cpp | 39 +++++++++++++++++ src/TUI/tui_data.cpp | 29 +++++++++++++ src/main.cpp | 2 +- src/reHDD.cpp | 93 ++++++++++++++++++++++++++++++++++++++--- vcCodium.code-workspace | 6 ++- 10 files changed, 264 insertions(+), 10 deletions(-) create mode 100644 include/tui.h create mode 100644 include/tui_data.h create mode 100644 src/TUI/tui.cpp create mode 100644 src/TUI/tui_data.cpp diff --git a/include/drive.h b/include/drive.h index 3d7f3b1..bfd33da 100644 --- a/include/drive.h +++ b/include/drive.h @@ -46,6 +46,11 @@ private: uint32_t u32ErrorCount = 0U; uint32_t u32PowerOnHours = 0U; //in hours uint32_t u32PowerCycles = 0U; + uint32_t u32ShredPercentage = 0U; //in percent + + + + }; #endif // DRIVE_H_ \ No newline at end of file diff --git a/include/reHDD.h b/include/reHDD.h index a05e1ad..46a7d05 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -15,12 +15,17 @@ #include #include #include +#include +#include +#include using namespace std; #include "drive.h" #include "smart.h" #include "wipe.h" +//#include "tui.h" +//#include "tui_data.h" template T* iterator_to_pointer(I i) { @@ -40,9 +45,16 @@ private: vector vecDrives; //stores all drive data void searchDrives(vector * pvecDrives); + void printDrives(vector * pvecDrives); void filterIgnoredDrives(vector * pvecDrives); void addSMARTData(vector * pvecDrives); + + static void ThreadDevices(); + + + + }; diff --git a/include/tui.h b/include/tui.h new file mode 100644 index 0000000..bcdb7e6 --- /dev/null +++ b/include/tui.h @@ -0,0 +1,41 @@ +/** + * @file tui.h + * @brief display user interface + * @author hendrik schutter + * @date 03.08.2020 + */ + +#ifndef TUI_H_ +#define TUI_H_ + +#include "reHDD.h" + +#define COLOR_AREA_STDSCR 1 +#define COLOR_AREA_OVERVIEW 2 +#define COLOR_AREA_ENTRY 3 + +#define COLOR_GRAY 8 + +class TUI +{ +protected: + +public: + + TUI(void); + + void initTUI(); + + void updateTUI(TUI_DATA data); + + + +private: + + void centerTitle(WINDOW *pwin, const char * title); + + + +}; + +#endif // TUI_H_ \ No newline at end of file diff --git a/include/tui_data.h b/include/tui_data.h new file mode 100644 index 0000000..3d5a383 --- /dev/null +++ b/include/tui_data.h @@ -0,0 +1,45 @@ +/** + * @file tui_data.h + * @brief ui model data + * @author hendrik schutter + * @date 03.08.2020 + */ + +#ifndef TUI_DATA_H_ +#define TUI_DATA_H_ + +#include "reHDD.h" + +#define COLOR_AREA_STDSCR 1 +#define COLOR_AREA_OVERVIEW 2 +#define COLOR_AREA_ENTRY 3 + +#define COLOR_GRAY 8 + + +class TUI_DATA +{ +protected: + +public: + + TUI_DATA(vector * pvecDrives); + + + + + + + +private: + + + + + string sCpuUsage; + string sRamUsage; + string sLocalTime; + +}; + +#endif // TUI_DATA_H_ \ No newline at end of file diff --git a/makefile b/makefile index c76a225..5c4fced 100644 --- a/makefile +++ b/makefile @@ -18,7 +18,7 @@ DCOMPILE_FLAGS = -D DEBUG # Add additional include paths INCLUDES = include # General linker settings -LINK_FLAGS = +LINK_FLAGS = -lpthread # Doc DOCDIR = doc #### END PROJECT SETTINGS #### diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp new file mode 100644 index 0000000..b65f90a --- /dev/null +++ b/src/TUI/tui.cpp @@ -0,0 +1,39 @@ +/** + * @file tui.cpp + * @brief display user interface + * @author hendrik schutter + * @date 03.08.2020 + */ +/* +#include "../include/reHDD.h" +*/ + +/** + * \brief wipe drive with shred + * \param pointer of Drive instance + * \return void + */ +/* +void TUI::initTUI() +{ + initscr(); + raw(); + keypad(stdscr,TRUE); + if(has_colors() == TRUE) { + start_color(); + } else { + printf("Your terminal does not support color\n"); + exit(1); + } + clear(); + curs_set(0); + init_color(COLOR_GRAY, 173, 170, 173); +} + +void TUI::updateTUI(TUI_DATA data){ + + + + + +}*/ \ No newline at end of file diff --git a/src/TUI/tui_data.cpp b/src/TUI/tui_data.cpp new file mode 100644 index 0000000..b0255fa --- /dev/null +++ b/src/TUI/tui_data.cpp @@ -0,0 +1,29 @@ +/** + * @file tui.cpp + * @brief display user interface + * @author hendrik schutter + * @date 03.08.2020 + */ + +//#include "../include/reHDD.h" + +/* + + TUI_DATA::TUI_DATA(vector * pvecDrives) +{ + + + + + + + + +} + + + + + + +*/ diff --git a/src/main.cpp b/src/main.cpp index 4978cb8..6f287a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ */ int main(void) { - cout << "refurbishingHddTool" << endl; + // cout << "refurbishingHddTool" << endl; reHDD* app = new reHDD(); app->app_logic(); diff --git a/src/reHDD.cpp b/src/reHDD.cpp index ec03080..72780b7 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -7,6 +7,9 @@ #include "../include/reHDD.h" + +static int fd[2];//File descriptor for creating a pipe + /** * \brief app constructor * \param void @@ -26,10 +29,44 @@ void reHDD::app_logic(void) { cout << "app logic" << endl; - searchDrives(&vecDrives); //search for new drives and store them in list - filterIgnoredDrives(&vecDrives); //filter out ignored drives - addSMARTData(&vecDrives); //add S.M.A.R.T. Data to the drives - printDrives(&vecDrives); //print currently attached drives + thread thDevices(ThreadDevices); + + + //searchDrives(&vecDrives); //search for new drives and store them in list + //filterIgnoredDrives(&vecDrives); //filter out ignored drives + //addSMARTData(&vecDrives); //add S.M.A.R.T. Data to the drives + // printDrives(&vecDrives); //print currently attached drives + + + + int result = pipe (fd); + +while(1){ + char ch; + + + result = read (fd[0],&ch,1); + + if (result != 1) { + perror("read"); + exit(3); + } + + printf ("From Main Thread: %c\n", ch); + + + } + + + + + thDevices.join(); + + std::cout << std::endl; + +/* + + size_t u64SelectedDriveIndex = 0U; size_t u64DriveVecSize = (vecDrives.size()); @@ -39,18 +76,60 @@ void reHDD::app_logic(void) cout << "Selected drive index: " << u64SelectedDriveIndex << endl; if(u64SelectedDriveIndex < (u64DriveVecSize)) { - Wipe::wipeDrive(&vecDrives[u64SelectedDriveIndex]); + // Wipe::wipeDrive(&vecDrives[u64SelectedDriveIndex]); } +*/ +} + +void reHDD::ThreadDevices(){ + + + +while(true){ + +cout << "Thread" << endl; + + + + + + + + + + + + + + + int result = write (fd[1], "A",1); + //cout << result << endl; + if (result != 1){ + perror ("write error"); + // exit (2); + } + + sleep(5); //sleep 5 sec +} + + } + + /** * \brief search attached drives on /dev/sd* * \param pointer of vector * pvecDrives * \return void */ -void reHDD::searchDrives(vector * pvecDrives) + void reHDD::searchDrives(vector * pvecDrives) { + + + + + cout << "search drives ..." << endl; char * cLine = NULL; size_t len = 0; @@ -71,6 +150,8 @@ void reHDD::searchDrives(vector * pvecDrives) } } fclose(outputfileHwinfo); + + } /** diff --git a/vcCodium.code-workspace b/vcCodium.code-workspace index 6b6425d..9860bd5 100644 --- a/vcCodium.code-workspace +++ b/vcCodium.code-workspace @@ -12,7 +12,9 @@ "unordered_map": "cpp", "string_view": "cpp", "ostream": "cpp", - "chrono": "cpp" - } + "chrono": "cpp", + "thread": "cpp" + }, + "git.ignoreLimitWarning": true } } \ No newline at end of file From a61d4321f6ea749910e3115aebddbc176ee6f64a Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 4 Aug 2020 11:59:45 +0200 Subject: [PATCH 02/41] added ipc --- include/reHDD.h | 23 +++++++++++---- src/reHDD.cpp | 64 +++++++++++++++++++++++++++++------------ vcCodium.code-workspace | 49 ++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 25 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 46a7d05..48fd308 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -18,6 +18,9 @@ #include #include #include +#include +#include + using namespace std; @@ -32,6 +35,8 @@ template T* iterator_to_pointer(I i) return (&(*i)); } + + class reHDD { protected: @@ -44,13 +49,21 @@ private: vector vecDrives; //stores all drive data - void searchDrives(vector * pvecDrives); + static void searchDrives(vector * pvecDrives); + + static void printDrives(vector * pvecDrives); + + + static void filterIgnoredDrives(vector * pvecDrives); + + + static void addSMARTData(vector * pvecDrives); + + static void ThreadScannDevices(); + + - void printDrives(vector * pvecDrives); - void filterIgnoredDrives(vector * pvecDrives); - void addSMARTData(vector * pvecDrives); - static void ThreadDevices(); diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 72780b7..ddb783f 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -8,7 +8,16 @@ #include "../include/reHDD.h" -static int fd[2];//File descriptor for creating a pipe +static int fdSearchDrives[2];//File descriptor for pipe that informs if new drives are found + +static int fdUserInput[2];//File descriptor for pipe that informs if a user input occoures + +static std::mutex mxScannDrives; + +static vector vecNewDrives; //store found drives that are updated every 5sec + + +static fd_set selectSet; /** * \brief app constructor @@ -29,23 +38,28 @@ void reHDD::app_logic(void) { cout << "app logic" << endl; - thread thDevices(ThreadDevices); + int result = pipe(fdSearchDrives); - //searchDrives(&vecDrives); //search for new drives and store them in list - //filterIgnoredDrives(&vecDrives); //filter out ignored drives - //addSMARTData(&vecDrives); //add S.M.A.R.T. Data to the drives - // printDrives(&vecDrives); //print currently attached drives + FD_ZERO(&selectSet); + FD_SET(fdSearchDrives[0], &selectSet); + thread thDevices(ThreadScannDevices); - - int result = pipe (fd); + while(1){ + + int iSelectReturn = select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); + + + if( FD_ISSET(fdSearchDrives[0], &selectSet)) { + + char ch; - result = read (fd[0],&ch,1); + result = read (fdSearchDrives[0],&ch,1); if (result != 1) { perror("read"); @@ -54,6 +68,17 @@ while(1){ printf ("From Main Thread: %c\n", ch); + mxScannDrives.lock(); + printDrives(&vecNewDrives); + //replace with old list + // action if needed + mxScannDrives.unlock(); + + } + + + + } @@ -62,8 +87,6 @@ while(1){ thDevices.join(); - std::cout << std::endl; - /* @@ -81,7 +104,7 @@ while(1){ */ } -void reHDD::ThreadDevices(){ +void reHDD::ThreadScannDevices(){ @@ -90,19 +113,22 @@ while(true){ cout << "Thread" << endl; + mxScannDrives.lock(); + vecNewDrives.clear(); + + + searchDrives(&vecNewDrives); //search for new drives and store them in list + filterIgnoredDrives(&vecNewDrives); //filter out ignored drives + addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives + + mxScannDrives.unlock(); - - - - - - - int result = write (fd[1], "A",1); + int result = write (fdSearchDrives[1], "A",1); //cout << result << endl; if (result != 1){ perror ("write error"); diff --git a/vcCodium.code-workspace b/vcCodium.code-workspace index 9860bd5..9feb538 100644 --- a/vcCodium.code-workspace +++ b/vcCodium.code-workspace @@ -13,7 +13,54 @@ "string_view": "cpp", "ostream": "cpp", "chrono": "cpp", - "thread": "cpp" + "thread": "cpp", + "deque": "cpp", + "vector": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ranges": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp" }, "git.ignoreLimitWarning": true } From 27b48de32a748d338a815a4737b3934ae4131509 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 4 Aug 2020 17:18:32 +0200 Subject: [PATCH 03/41] added filter algo for existing drives --- ignoreDrives.conf | 2 +- include/drive.h | 4 +- include/reHDD.h | 9 +-- include/tui.h | 10 +-- include/tui_data.h | 18 +++--- src/TUI/tui_data.cpp | 2 +- src/main.cpp | 2 +- src/reHDD.cpp | 142 +++++++++++++++++++------------------------ src/wipe.cpp | 6 +- 9 files changed, 90 insertions(+), 105 deletions(-) diff --git a/ignoreDrives.conf b/ignoreDrives.conf index aef73e8..0605f89 100644 --- a/ignoreDrives.conf +++ b/ignoreDrives.conf @@ -1,3 +1,3 @@ +/dev/sdc:4673974d-1af2-44fd-996b-a2d8e4c43d9a /dev/sda:508ef27d-5039-4e8b-9e2c-22d7528b7149 /dev/sdb:07f4ad14-c4b6-46e7-9cdf-3cfa9841d53d -/dev/sdc:4673974d-1af2-44fd-996b-a2d8e4c43d9a diff --git a/include/drive.h b/include/drive.h index bfd33da..3f96677 100644 --- a/include/drive.h +++ b/include/drive.h @@ -48,8 +48,8 @@ private: uint32_t u32PowerCycles = 0U; uint32_t u32ShredPercentage = 0U; //in percent - - + + }; diff --git a/include/reHDD.h b/include/reHDD.h index 48fd308..94a5a06 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -16,10 +16,11 @@ #include #include #include -#include +#include #include #include #include +#include using namespace std; @@ -49,12 +50,12 @@ private: vector vecDrives; //stores all drive data - static void searchDrives(vector * pvecDrives); + static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); - static void filterIgnoredDrives(vector * pvecDrives); + static void filterIgnoredDrives(vector * pvecDrives); static void addSMARTData(vector * pvecDrives); @@ -62,11 +63,11 @@ private: static void ThreadScannDevices(); + void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); - }; diff --git a/include/tui.h b/include/tui.h index bcdb7e6..3266e70 100644 --- a/include/tui.h +++ b/include/tui.h @@ -22,17 +22,17 @@ protected: public: - TUI(void); + TUI(void); - void initTUI(); + void initTUI(); void updateTUI(TUI_DATA data); - + private: - - void centerTitle(WINDOW *pwin, const char * title); + + void centerTitle(WINDOW *pwin, const char * title); diff --git a/include/tui_data.h b/include/tui_data.h index 3d5a383..5dab10f 100644 --- a/include/tui_data.h +++ b/include/tui_data.h @@ -23,22 +23,22 @@ protected: public: - TUI_DATA(vector * pvecDrives); - - + TUI_DATA(vector * pvecDrives); + + + - private: - - - string sCpuUsage; - string sRamUsage; - string sLocalTime; + + + string sCpuUsage; + string sRamUsage; + string sLocalTime; }; diff --git a/src/TUI/tui_data.cpp b/src/TUI/tui_data.cpp index b0255fa..8fc2ad8 100644 --- a/src/TUI/tui_data.cpp +++ b/src/TUI/tui_data.cpp @@ -11,7 +11,7 @@ TUI_DATA::TUI_DATA(vector * pvecDrives) { - + diff --git a/src/main.cpp b/src/main.cpp index 6f287a6..96a915e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ */ int main(void) { - // cout << "refurbishingHddTool" << endl; + // cout << "refurbishingHddTool" << endl; reHDD* app = new reHDD(); app->app_logic(); diff --git a/src/reHDD.cpp b/src/reHDD.cpp index ddb783f..4185445 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -39,123 +39,106 @@ void reHDD::app_logic(void) cout << "app logic" << endl; - int result = pipe(fdSearchDrives); + pipe(fdSearchDrives); FD_ZERO(&selectSet); FD_SET(fdSearchDrives[0], &selectSet); - thread thDevices(ThreadScannDevices); + thread thDevices(ThreadScannDevices); //start thread that scanns for drives - + while(1) { -while(1){ + select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); - int iSelectReturn = select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); + if( FD_ISSET(fdSearchDrives[0], &selectSet)) { + char dummy; + read (fdSearchDrives[0],&dummy,1); + mxScannDrives.lock(); + printDrives(&vecNewDrives); + //replace with old list + // action if needed - if( FD_ISSET(fdSearchDrives[0], &selectSet)) { - - char ch; - - - result = read (fdSearchDrives[0],&ch,1); - - if (result != 1) { - perror("read"); - exit(3); - } - - printf ("From Main Thread: %c\n", ch); - - mxScannDrives.lock(); - printDrives(&vecNewDrives); - //replace with old list - // action if needed - mxScannDrives.unlock(); - - } + filterNewDrives(&vecDrives, &vecDrives); - + mxScannDrives.unlock(); } + } thDevices.join(); -/* + /* - size_t u64SelectedDriveIndex = 0U; - size_t u64DriveVecSize = (vecDrives.size()); + size_t u64SelectedDriveIndex = 0U; + size_t u64DriveVecSize = (vecDrives.size()); - cout << "Select drive to wipe:" << endl; - cin >> u64SelectedDriveIndex; - cout << "Selected drive index: " << u64SelectedDriveIndex << endl; + cout << "Select drive to wipe:" << endl; + cin >> u64SelectedDriveIndex; + cout << "Selected drive index: " << u64SelectedDriveIndex << endl; - if(u64SelectedDriveIndex < (u64DriveVecSize)) { - // Wipe::wipeDrive(&vecDrives[u64SelectedDriveIndex]); + if(u64SelectedDriveIndex < (u64DriveVecSize)) { + // Wipe::wipeDrive(&vecDrives[u64SelectedDriveIndex]); + } + */ +} + +void reHDD::ThreadScannDevices() { + while(true) { + cout << "Thread" << endl; + mxScannDrives.lock(); + vecNewDrives.clear(); + searchDrives(&vecNewDrives); //search for new drives and store them in list + filterIgnoredDrives(&vecNewDrives); //filter out ignored drives + addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives + mxScannDrives.unlock(); + write (fdSearchDrives[1], "A",1); + sleep(5); //sleep 5 sec } -*/ } -void reHDD::ThreadScannDevices(){ +void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives){ + vector ::iterator itOld; //Iterator for current (old) drive list + vector ::iterator itNew; //Iterator for new drive list that was created from to scann thread -while(true){ + vector vecOfflineDrives; //TODO -cout << "Thread" << endl; - - - mxScannDrives.lock(); - vecNewDrives.clear(); - - - searchDrives(&vecNewDrives); //search for new drives and store them in list - filterIgnoredDrives(&vecNewDrives); //filter out ignored drives - addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives - - mxScannDrives.unlock(); - - - - - - - int result = write (fdSearchDrives[1], "A",1); - //cout << result << endl; - if (result != 1){ - perror ("write error"); - // exit (2); - } - - sleep(5); //sleep 5 sec -} + for (itOld = pvecOldDrives->begin(); itOld != pvecOldDrives->end(); ++itOld) + { + for (itNew = pvecOldDrives->begin(); itNew != pvecOldDrives->end(); ++itNew) + { + if(itOld->getSerial() == itNew->getSerial()){ + //drive exists already --> remove it from old list + pvecOldDrives->erase(itOld); + } + } + } + copy(pvecOldDrives->begin(), pvecOldDrives->end(), back_inserter(vecOfflineDrives)); + cout << "start offline" << endl; + printDrives(&vecOfflineDrives); + cout << "end offline" << endl; } - - /** * \brief search attached drives on /dev/sd* * \param pointer of vector * pvecDrives * \return void */ - void reHDD::searchDrives(vector * pvecDrives) +void reHDD::searchDrives(vector * pvecDrives) { - - - - - cout << "search drives ..." << endl; char * cLine = NULL; size_t len = 0; @@ -176,8 +159,6 @@ cout << "Thread" << endl; } } fclose(outputfileHwinfo); - - } /** @@ -213,10 +194,11 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) vtlIgnoredDevices.emplace_back(sIgnoredDrivePath, sIgnoredDriveUUID); //add found path and uuid from ingnore file to vector } } - //loop through found entries in ingnore file for(auto row : vtlIgnoredDevices) { + cout << "file drive path found: " << get<0>(row) << endl; + auto it = pvecDrives->begin(); while (it != pvecDrives->end()) { @@ -224,12 +206,12 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) string sUUID; if (!get<0>(row).compare(it->getPath())) //find same drive based on path { - // cout << "Same drive path found" << endl; + cout << "Same drive path found: " << it->getPath() << endl; char * cLine = NULL; size_t len = 0; string sCMD = "blkid "; sCMD.append(it->getPath()); - // cout << "cmd: " << sCMD << endl; + cout << "cmd: " << sCMD << endl; FILE* outputfileBlkid = popen(sCMD.c_str(), "r"); //get UUID from drive if (outputfileBlkid == NULL) { @@ -248,7 +230,7 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) } } fclose(outputfileBlkid); - // cout << "blkid uuid:" << sUUID << endl; + cout << "blkid uuid:" << sUUID << endl; if (get<1>(row).compare(sUUID)) //compare uuid from ignore file and uuid from drive { @@ -259,6 +241,8 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) { // same uuid found than in ignore file --> ignore this drive it = pvecDrives->erase(it); + cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; + } } diff --git a/src/wipe.cpp b/src/wipe.cpp index a5626f9..827328c 100644 --- a/src/wipe.cpp +++ b/src/wipe.cpp @@ -50,19 +50,19 @@ void Wipe::wipeDrive(Drive* drive) uint64_t u64minutes = 0U; uint64_t u64seconds = 0U; - while(u64TimeMS >= 1000) + while(u64TimeMS >= 1000) { u64seconds++; u64TimeMS = u64TimeMS - 1000; } - while(u64seconds >= 60) + while(u64seconds >= 60) { u64minutes++; u64seconds = u64seconds - 60; } - while(u64minutes >= 60) + while(u64minutes >= 60) { u64hours++; u64minutes = u64minutes - 60; From 89f900c970d506de79584ebded44e766b0a60c58 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 4 Aug 2020 19:51:34 +0200 Subject: [PATCH 04/41] detect offline drives --- ignoreDrives.conf | 2 +- include/reHDD.h | 2 ++ src/reHDD.cpp | 57 ++++++++++++++++++++++++----------------------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/ignoreDrives.conf b/ignoreDrives.conf index 0605f89..ed2b36a 100644 --- a/ignoreDrives.conf +++ b/ignoreDrives.conf @@ -1,3 +1,3 @@ /dev/sdc:4673974d-1af2-44fd-996b-a2d8e4c43d9a /dev/sda:508ef27d-5039-4e8b-9e2c-22d7528b7149 -/dev/sdb:07f4ad14-c4b6-46e7-9cdf-3cfa9841d53d +/dev/sdb:32b66944-ffa0-40e9-817c-3f0c52eefaf4 diff --git a/include/reHDD.h b/include/reHDD.h index 94a5a06..32390cd 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,6 +8,8 @@ #ifndef REHDD_H_ #define REHDD_H_ +#define DRYRUN + #include #include #include diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 4185445..6042f85 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -46,6 +46,7 @@ void reHDD::app_logic(void) thread thDevices(ThreadScannDevices); //start thread that scanns for drives + while(1) { select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); @@ -54,16 +55,16 @@ void reHDD::app_logic(void) char dummy; read (fdSearchDrives[0],&dummy,1); mxScannDrives.lock(); - printDrives(&vecNewDrives); + //replace with old list // action if needed - filterNewDrives(&vecDrives, &vecDrives); - + filterNewDrives(&vecDrives, &vecNewDrives); + printDrives(&vecDrives); mxScannDrives.unlock(); } @@ -72,7 +73,7 @@ void reHDD::app_logic(void) - thDevices.join(); + // thDevices.join(); /* @@ -105,30 +106,33 @@ void reHDD::ThreadScannDevices() { } } -void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives){ - +void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { vector ::iterator itOld; //Iterator for current (old) drive list vector ::iterator itNew; //Iterator for new drive list that was created from to scann thread - vector vecOfflineDrives; //TODO - for (itOld = pvecOldDrives->begin(); itOld != pvecOldDrives->end(); ++itOld) { - for (itNew = pvecOldDrives->begin(); itNew != pvecOldDrives->end(); ++itNew) + bool bOldDriveIsOffline = true; + for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end(); ++itNew) { - if(itOld->getSerial() == itNew->getSerial()){ - //drive exists already --> remove it from old list - pvecOldDrives->erase(itOld); + if(itOld->getSerial() == itNew->getSerial()) { + bOldDriveIsOffline = false; + // cout << "already online drive found: " << itOld->getPath() << endl; } } - } - copy(pvecOldDrives->begin(), pvecOldDrives->end(), back_inserter(vecOfflineDrives)); + if(bOldDriveIsOffline == true) { + cout << "offline drive found: " << itOld->getPath() << endl; + //TODO kill wipe thread + } + } - cout << "start offline" << endl; - printDrives(&vecOfflineDrives); - cout << "end offline" << endl; + pvecOldDrives->clear(); + + for (long unsigned int i=0; isize(); i++) { + pvecOldDrives->push_back((*pvecNewDrives)[i]); + } } @@ -174,6 +178,8 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) vector> vtlIgnoredDevices; //store drives from ingnore file + //vector vecTmpDrives + ifstream input( "ignoreDrives.conf" ); //read ingnore file for(string sLine; getline( input, sLine );) @@ -197,21 +203,17 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) //loop through found entries in ingnore file for(auto row : vtlIgnoredDevices) { - cout << "file drive path found: " << get<0>(row) << endl; - - auto it = pvecDrives->begin(); - while (it != pvecDrives->end()) + vector ::iterator it; + for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { - it++; string sUUID; if (!get<0>(row).compare(it->getPath())) //find same drive based on path { - cout << "Same drive path found: " << it->getPath() << endl; char * cLine = NULL; size_t len = 0; string sCMD = "blkid "; sCMD.append(it->getPath()); - cout << "cmd: " << sCMD << endl; + //cout << "cmd: " << sCMD << endl; FILE* outputfileBlkid = popen(sCMD.c_str(), "r"); //get UUID from drive if (outputfileBlkid == NULL) { @@ -230,7 +232,7 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) } } fclose(outputfileBlkid); - cout << "blkid uuid:" << sUUID << endl; + //cout << "blkid uuid:" << sUUID << endl; if (get<1>(row).compare(sUUID)) //compare uuid from ignore file and uuid from drive { @@ -241,10 +243,9 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) { // same uuid found than in ignore file --> ignore this drive it = pvecDrives->erase(it); - cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; - + it--; + //cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; } - } } } From 9322ea65a7ed8958b5309d5f4f951015edf2ef12 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 4 Aug 2020 22:35:29 +0200 Subject: [PATCH 05/41] started to use the UI --- include/drive.h | 2 + include/reHDD.h | 14 ++--- include/tui.h | 19 +++--- include/tui_data.h | 45 --------------- makefile | 2 +- src/TUI/tui.cpp | 135 +++++++++++++++++++++++++++++++++++++++++-- src/TUI/tui_data.cpp | 29 ---------- src/drive.cpp | 12 ++++ src/reHDD.cpp | 53 +++++++---------- 9 files changed, 180 insertions(+), 131 deletions(-) delete mode 100644 include/tui_data.h delete mode 100644 src/TUI/tui_data.cpp diff --git a/include/drive.h b/include/drive.h index 3f96677..53bad36 100644 --- a/include/drive.h +++ b/include/drive.h @@ -37,6 +37,8 @@ public: uint32_t powerOnHours, uint32_t powerCycles); + string sCapacityToText(); + private: string sPath; string sModelFamily; diff --git a/include/reHDD.h b/include/reHDD.h index 32390cd..bafaefb 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -23,6 +23,7 @@ #include #include #include +#include using namespace std; @@ -30,8 +31,8 @@ using namespace std; #include "drive.h" #include "smart.h" #include "wipe.h" -//#include "tui.h" -//#include "tui_data.h" +#include "tui.h" + template T* iterator_to_pointer(I i) { @@ -53,21 +54,14 @@ private: vector vecDrives; //stores all drive data static void searchDrives(vector * pvecDrives); - static void printDrives(vector * pvecDrives); - - static void filterIgnoredDrives(vector * pvecDrives); - - static void addSMARTData(vector * pvecDrives); - static void ThreadScannDevices(); - void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); - + TUI *ui; diff --git a/include/tui.h b/include/tui.h index 3266e70..36a14dc 100644 --- a/include/tui.h +++ b/include/tui.h @@ -24,18 +24,23 @@ public: TUI(void); - void initTUI(); - - void updateTUI(TUI_DATA data); - + static void initTUI(); + void updateTUI(vector * pvecDrives); private: + static string sCpuUsage; + static string sRamUsage; + static string sLocalTime; - void centerTitle(WINDOW *pwin, const char * title); - - + WINDOW *detailview; + WINDOW *overview; + vector vWinDriveEntries; + static void centerTitle(WINDOW *pwin, const char * title); + static WINDOW *createOverViewWindow( int iXSize, int iYSize); + static WINDOW *createDetailViewWindow( int iXSize, int iYSize, int iXStart); + static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity); }; #endif // TUI_H_ \ No newline at end of file diff --git a/include/tui_data.h b/include/tui_data.h deleted file mode 100644 index 5dab10f..0000000 --- a/include/tui_data.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @file tui_data.h - * @brief ui model data - * @author hendrik schutter - * @date 03.08.2020 - */ - -#ifndef TUI_DATA_H_ -#define TUI_DATA_H_ - -#include "reHDD.h" - -#define COLOR_AREA_STDSCR 1 -#define COLOR_AREA_OVERVIEW 2 -#define COLOR_AREA_ENTRY 3 - -#define COLOR_GRAY 8 - - -class TUI_DATA -{ -protected: - -public: - - TUI_DATA(vector * pvecDrives); - - - - - - - -private: - - - - - string sCpuUsage; - string sRamUsage; - string sLocalTime; - -}; - -#endif // TUI_DATA_H_ \ No newline at end of file diff --git a/makefile b/makefile index 5c4fced..66f9bac 100644 --- a/makefile +++ b/makefile @@ -18,7 +18,7 @@ DCOMPILE_FLAGS = -D DEBUG # Add additional include paths INCLUDES = include # General linker settings -LINK_FLAGS = -lpthread +LINK_FLAGS = -lpthread -lncurses # Doc DOCDIR = doc #### END PROJECT SETTINGS #### diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index b65f90a..825475a 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -4,16 +4,23 @@ * @author hendrik schutter * @date 03.08.2020 */ -/* -#include "../include/reHDD.h" -*/ + +#include "../../include/reHDD.h" + + +TUI::TUI(void){ + + + +} + /** * \brief wipe drive with shred * \param pointer of Drive instance * \return void */ -/* + void TUI::initTUI() { initscr(); @@ -30,10 +37,126 @@ void TUI::initTUI() init_color(COLOR_GRAY, 173, 170, 173); } -void TUI::updateTUI(TUI_DATA data){ +void TUI::updateTUI(vector * pvecDrives) { + + + werase(stdscr); + + int stdscrX, stdscrY; + getmaxyx(stdscr, stdscrY, stdscrX); + + init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); + wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); + + mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); + + refresh(); + + overview=createOverViewWindow((int)(stdscrX/3), (stdscrY-15)); + wrefresh(overview); + + detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5); + wrefresh(detailview); + + vWinDriveEntries.clear(); +int i = 0; + + vector ::iterator it; + for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) + { + + string sModelFamily = it->getModelFamily(); + string sModelName = it->getModelName(); + string sCapacity = it->sCapacityToText(); + + + + // WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); + WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (i) )+3, sModelFamily, sModelName, sCapacity); -}*/ \ No newline at end of file + // vWinDriveEntries.push_back(tmp); + wrefresh(tmp); + + i++; + } + + +} + + +void TUI::centerTitle(WINDOW *pwin, const char * title) { + int x, maxX, stringSize; + getmaxyx(pwin, maxX, maxX); + stringSize = 4 + strlen(title); + x = (maxX - stringSize)/2; + mvwaddch(pwin, 0, x, ACS_RTEE); + waddch(pwin, ' '); + waddstr(pwin, title); + waddch(pwin, ' '); + waddch(pwin, ACS_LTEE); +} + +WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) { + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, 2, 2); + init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + + time_t rawtime; + struct tm * timeinfo; + char buffer[80]; + + time (&rawtime); + timeinfo = localtime(&rawtime); + + strftime(buffer,sizeof(buffer),"%d-%m-%Y %H:%M:%S",timeinfo); + std::string str(buffer); + + + centerTitle(newWindow, str.c_str()); + + return newWindow; +} + + +WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, 2, iXStart); + init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); + box(newWindow, ACS_VLINE, ACS_HLINE); + centerTitle(newWindow, "Selected Drive: xyz"); + + // mvaddstr(iXStart+1, 5, "Test"); + + return newWindow; +} + +WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity) { + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, iYStart, iXStart); + init_pair(COLOR_AREA_ENTRY, COLOR_BLACK, COLOR_GRAY); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + attron(COLOR_PAIR(COLOR_AREA_ENTRY)); + mvaddstr(iYStart+1, 5, "Test"); + + //addstr("test"); + + + //cout << "X: " << sModelFamily << endl; + + // mvaddstr(iYStart+2, 5, sModelName.c_str()); + //mvaddstr(iYStart+3, 5, sCapacity.c_str()); + + refresh(); + + return newWindow; +} diff --git a/src/TUI/tui_data.cpp b/src/TUI/tui_data.cpp deleted file mode 100644 index 8fc2ad8..0000000 --- a/src/TUI/tui_data.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @file tui.cpp - * @brief display user interface - * @author hendrik schutter - * @date 03.08.2020 - */ - -//#include "../include/reHDD.h" - -/* - - TUI_DATA::TUI_DATA(vector * pvecDrives) -{ - - - - - - - - -} - - - - - - -*/ diff --git a/src/drive.cpp b/src/drive.cpp index c597b1e..f957170 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -46,6 +46,18 @@ uint32_t Drive::getPowerCycles(void) return u32PowerCycles; } +string Drive::sCapacityToText(){ + if(getCapacity() <= (999*1000000000U)){ + // Less or even 999 GB --> GB + return to_string(getCapacity() / 1000000000U) + " GB"; + } + else{ + // More 999 GB --> TB + return to_string(getCapacity() / 1000000000000U) + " TB"; + } + return "ERROR"; +} + /** * \brief set S.M.A.R.T. values in model * \param string modelFamily diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 6042f85..d6f8055 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -12,6 +12,8 @@ static int fdSearchDrives[2];//File descriptor for pipe that informs if new driv static int fdUserInput[2];//File descriptor for pipe that informs if a user input occoures +static int fdWhipe[2];//File descriptor for pipe that informs if a wipe thread signals + static std::mutex mxScannDrives; static vector vecNewDrives; //store found drives that are updated every 5sec @@ -27,6 +29,8 @@ static fd_set selectSet; reHDD::reHDD(void) { cout << "created app" << endl; + + } /** @@ -38,15 +42,19 @@ void reHDD::app_logic(void) { cout << "app logic" << endl; + ui = new TUI(); + + ui->initTUI(); pipe(fdSearchDrives); + pipe(fdWhipe); FD_ZERO(&selectSet); FD_SET(fdSearchDrives[0], &selectSet); + FD_SET(fdWhipe[0], &selectSet); thread thDevices(ThreadScannDevices); //start thread that scanns for drives - while(1) { select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); @@ -55,46 +63,24 @@ void reHDD::app_logic(void) char dummy; read (fdSearchDrives[0],&dummy,1); mxScannDrives.lock(); - - //replace with old list - // action if needed - - - filterNewDrives(&vecDrives, &vecNewDrives); + //printDrives(&vecDrives); + //TODO update UI - - printDrives(&vecDrives); + ui->updateTUI(&vecDrives); mxScannDrives.unlock(); } - } - - - - - // thDevices.join(); - - /* - - - - size_t u64SelectedDriveIndex = 0U; - size_t u64DriveVecSize = (vecDrives.size()); - - cout << "Select drive to wipe:" << endl; - cin >> u64SelectedDriveIndex; - cout << "Selected drive index: " << u64SelectedDriveIndex << endl; - - if(u64SelectedDriveIndex < (u64DriveVecSize)) { - // Wipe::wipeDrive(&vecDrives[u64SelectedDriveIndex]); + else if (FD_ISSET(fdWhipe[0], &selectSet)) { + cout << "Whipe signal" << endl; } - */ + } //endless loop + thDevices.join(); } void reHDD::ThreadScannDevices() { while(true) { - cout << "Thread" << endl; + // cout << "Thread" << endl; mxScannDrives.lock(); vecNewDrives.clear(); searchDrives(&vecNewDrives); //search for new drives and store them in list @@ -106,6 +92,7 @@ void reHDD::ThreadScannDevices() { } } + void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { vector ::iterator itOld; //Iterator for current (old) drive list @@ -118,7 +105,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN { if(itOld->getSerial() == itNew->getSerial()) { bOldDriveIsOffline = false; - // cout << "already online drive found: " << itOld->getPath() << endl; + // cout << "already online drive found: " << itOld->getPath() << endl; } } @@ -143,7 +130,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN */ void reHDD::searchDrives(vector * pvecDrives) { - cout << "search drives ..." << endl; + // cout << "search drives ..." << endl; char * cLine = NULL; size_t len = 0; From 4c6aa1f19cb95265cbc86594d68dc48782f75ffa Mon Sep 17 00:00:00 2001 From: localhorst Date: Thu, 6 Aug 2020 11:41:38 +0200 Subject: [PATCH 06/41] display text on sub-windows --- src/TUI/tui.cpp | 66 +++++++++++++++++++++++++------------------------ src/drive.cpp | 10 ++++---- src/reHDD.cpp | 8 +++--- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 825475a..34d9661 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -8,7 +8,7 @@ #include "../../include/reHDD.h" -TUI::TUI(void){ +TUI::TUI(void) { @@ -35,20 +35,29 @@ void TUI::initTUI() clear(); curs_set(0); init_color(COLOR_GRAY, 173, 170, 173); + + + init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); + wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); + + int stdscrX, stdscrY; + getmaxyx(stdscr, stdscrY, stdscrX); + mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); + } void TUI::updateTUI(vector * pvecDrives) { - - werase(stdscr); + + //werase(stdscr); int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); - - mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); + + // mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); refresh(); @@ -61,7 +70,7 @@ void TUI::updateTUI(vector * pvecDrives) { vWinDriveEntries.clear(); -int i = 0; + // int i = 0; vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) @@ -71,17 +80,14 @@ int i = 0; string sModelName = it->getModelName(); string sCapacity = it->sCapacityToText(); - + WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); - // WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); + // WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (i) )+3, sModelFamily, sModelName, sCapacity); - - WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (i) )+3, sModelFamily, sModelName, sCapacity); - - // vWinDriveEntries.push_back(tmp); + // vWinDriveEntries.push_back(tmp); wrefresh(tmp); - i++; + // i++; } @@ -107,20 +113,23 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) { wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); box(newWindow, ACS_VLINE, ACS_HLINE); - time_t rawtime; - struct tm * timeinfo; - char buffer[80]; + struct tm * timeinfo; + char buffer[80]; - time (&rawtime); - timeinfo = localtime(&rawtime); + time (&rawtime); + timeinfo = localtime(&rawtime); - strftime(buffer,sizeof(buffer),"%d-%m-%Y %H:%M:%S",timeinfo); - std::string str(buffer); + strftime(buffer,sizeof(buffer),"%d-%m-%Y %H:%M:%S",timeinfo); + std::string str(buffer); centerTitle(newWindow, str.c_str()); + //mvwaddstr(newWindow, 2, 3, "Drücke eine Taste"); + + //refresh(); + return newWindow; } @@ -133,8 +142,6 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { box(newWindow, ACS_VLINE, ACS_HLINE); centerTitle(newWindow, "Selected Drive: xyz"); - // mvaddstr(iXStart+1, 5, "Test"); - return newWindow; } @@ -146,17 +153,12 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, box(newWindow, ACS_VLINE, ACS_HLINE); attron(COLOR_PAIR(COLOR_AREA_ENTRY)); - mvaddstr(iYStart+1, 5, "Test"); + + mvwaddstr(newWindow,1, 1, sModelFamily.c_str()); + mvwaddstr(newWindow,2, 1, sModelName.c_str()); + mvwaddstr(newWindow,3, 1, sCapacity.c_str()); - //addstr("test"); - - - //cout << "X: " << sModelFamily << endl; - - // mvaddstr(iYStart+2, 5, sModelName.c_str()); - //mvaddstr(iYStart+3, 5, sCapacity.c_str()); - - refresh(); + // refresh(); return newWindow; } diff --git a/src/drive.cpp b/src/drive.cpp index f957170..661f538 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -46,13 +46,13 @@ uint32_t Drive::getPowerCycles(void) return u32PowerCycles; } -string Drive::sCapacityToText(){ - if(getCapacity() <= (999*1000000000U)){ +string Drive::sCapacityToText() { + if(getCapacity() <= (999*1000000000U)) { // Less or even 999 GB --> GB - return to_string(getCapacity() / 1000000000U) + " GB"; + return to_string(getCapacity() / 1000000000U) + " GB"; } - else{ - // More 999 GB --> TB + else { + // More 999 GB --> TB return to_string(getCapacity() / 1000000000000U) + " TB"; } return "ERROR"; diff --git a/src/reHDD.cpp b/src/reHDD.cpp index d6f8055..e5394de 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -30,7 +30,7 @@ reHDD::reHDD(void) { cout << "created app" << endl; - + } /** @@ -80,7 +80,7 @@ void reHDD::app_logic(void) void reHDD::ThreadScannDevices() { while(true) { - // cout << "Thread" << endl; + // cout << "Thread" << endl; mxScannDrives.lock(); vecNewDrives.clear(); searchDrives(&vecNewDrives); //search for new drives and store them in list @@ -110,7 +110,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN } if(bOldDriveIsOffline == true) { - cout << "offline drive found: " << itOld->getPath() << endl; + //cout << "offline drive found: " << itOld->getPath() << endl; //TODO kill wipe thread } } @@ -130,7 +130,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN */ void reHDD::searchDrives(vector * pvecDrives) { - // cout << "search drives ..." << endl; + // cout << "search drives ..." << endl; char * cLine = NULL; size_t len = 0; From c13182b77f8597850238d043875c1c3130877e07 Mon Sep 17 00:00:00 2001 From: localhorst Date: Thu, 6 Aug 2020 11:51:13 +0200 Subject: [PATCH 07/41] fixed capacity display --- src/drive.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/drive.cpp b/src/drive.cpp index 661f538..f816cda 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -47,13 +47,13 @@ uint32_t Drive::getPowerCycles(void) } string Drive::sCapacityToText() { - if(getCapacity() <= (999*1000000000U)) { + if(getCapacity() <= (999*1000000000UL)) { // Less or even 999 GB --> GB - return to_string(getCapacity() / 1000000000U) + " GB"; + return to_string(getCapacity() / 1000000000UL) + " GB"; } else { // More 999 GB --> TB - return to_string(getCapacity() / 1000000000000U) + " TB"; + return to_string(getCapacity() / 1000000000000UL) + " TB"; } return "ERROR"; } From a92a9c2a2a6c6fc0ee14076487d969ddcdb32604 Mon Sep 17 00:00:00 2001 From: localhorst Date: Thu, 6 Aug 2020 22:45:05 +0200 Subject: [PATCH 08/41] added user input via keys --- include/reHDD.h | 2 +- include/tui.h | 6 ++++- src/TUI/tui.cpp | 69 ++++++++++++++++++++++++++++--------------------- src/reHDD.cpp | 40 ++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 34 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index bafaefb..d09a638 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -40,7 +40,6 @@ template T* iterator_to_pointer(I i) } - class reHDD { protected: @@ -58,6 +57,7 @@ private: static void filterIgnoredDrives(vector * pvecDrives); static void addSMARTData(vector * pvecDrives); static void ThreadScannDevices(); + static void ThreadUserInput(); void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); diff --git a/include/tui.h b/include/tui.h index 36a14dc..9216a09 100644 --- a/include/tui.h +++ b/include/tui.h @@ -22,12 +22,16 @@ protected: public: + enum UserInput { UpKey, DownKey, Abort, Shred, Delete, Enter, ESC, Undefined}; + TUI(void); static void initTUI(); void updateTUI(vector * pvecDrives); + static enum UserInput readUserInput(); + private: static string sCpuUsage; static string sRamUsage; @@ -35,12 +39,12 @@ private: WINDOW *detailview; WINDOW *overview; - vector vWinDriveEntries; static void centerTitle(WINDOW *pwin, const char * title); static WINDOW *createOverViewWindow( int iXSize, int iYSize); static WINDOW *createDetailViewWindow( int iXSize, int iYSize, int iXStart); static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity); + }; #endif // TUI_H_ \ No newline at end of file diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 34d9661..dec36f0 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -34,31 +34,25 @@ void TUI::initTUI() } clear(); curs_set(0); + noecho(); + cbreak(); init_color(COLOR_GRAY, 173, 170, 173); - init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); - int stdscrX, stdscrY; - getmaxyx(stdscr, stdscrY, stdscrX); - mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); + mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); } void TUI::updateTUI(vector * pvecDrives) { - - //werase(stdscr); - int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); - // mvprintw(0, 2, "reHDD - HDD refurbishing tool - Licensed under GPL 3.0 X:%d Y:%d",stdscrX,stdscrY); - refresh(); overview=createOverViewWindow((int)(stdscrX/3), (stdscrY-15)); @@ -67,30 +61,48 @@ void TUI::updateTUI(vector * pvecDrives) { detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5); wrefresh(detailview); - vWinDriveEntries.clear(); - - - // int i = 0; - vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { - string sModelFamily = it->getModelFamily(); string sModelName = it->getModelName(); string sCapacity = it->sCapacityToText(); - WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); - - // WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (i) )+3, sModelFamily, sModelName, sCapacity); - - // vWinDriveEntries.push_back(tmp); + WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); wrefresh(tmp); - - // i++; } +} - +enum TUI::UserInput TUI::readUserInput() { + int ch = wgetch(stdscr); + switch(ch) + { + case KEY_UP: + return TUI::UserInput::UpKey; + break; + case KEY_DOWN: + return TUI::UserInput::DownKey; + break; + case 10: + return TUI::UserInput::Enter; + break; + case 27: + return TUI::UserInput::ESC; + break; + case 'a': + return TUI::UserInput::Abort; + break; + case 'd': + return TUI::UserInput::Delete; + break; + case 's': + return TUI::UserInput::Shred; + break; + default: + return TUI::UserInput::Undefined; + break; + } + return TUI::UserInput::Undefined; } @@ -126,14 +138,10 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) { centerTitle(newWindow, str.c_str()); - //mvwaddstr(newWindow, 2, 3, "Drücke eine Taste"); - - //refresh(); - + keypad(newWindow, TRUE); return newWindow; } - WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, 2, iXStart); @@ -142,6 +150,7 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { box(newWindow, ACS_VLINE, ACS_HLINE); centerTitle(newWindow, "Selected Drive: xyz"); + keypad(newWindow, TRUE); return newWindow; } @@ -153,12 +162,12 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, box(newWindow, ACS_VLINE, ACS_HLINE); attron(COLOR_PAIR(COLOR_AREA_ENTRY)); - + mvwaddstr(newWindow,1, 1, sModelFamily.c_str()); mvwaddstr(newWindow,2, 1, sModelName.c_str()); mvwaddstr(newWindow,3, 1, sCapacity.c_str()); - // refresh(); + keypad(newWindow, TRUE); return newWindow; } diff --git a/src/reHDD.cpp b/src/reHDD.cpp index e5394de..3d75f29 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -54,6 +54,7 @@ void reHDD::app_logic(void) FD_SET(fdWhipe[0], &selectSet); thread thDevices(ThreadScannDevices); //start thread that scanns for drives + thread thUserInput(ThreadUserInput); //start thread that reads user input while(1) { @@ -76,6 +77,7 @@ void reHDD::app_logic(void) } } //endless loop thDevices.join(); + thUserInput.join(); } void reHDD::ThreadScannDevices() { @@ -92,6 +94,42 @@ void reHDD::ThreadScannDevices() { } } +void reHDD::ThreadUserInput() { + while(true) { + + // cout << TUI::readUserInput() << endl; + switch (TUI::readUserInput()) + { + case TUI::UserInput::DownKey: + /* code */ + //cout << "Down" << endl; + break; + case TUI::UserInput::UpKey: + //cout << "Up" << endl; + break; + case TUI::UserInput::Undefined: + //cout << "Undefined" << endl; + break; + case TUI::UserInput::Abort: + //cout << "Abort" << endl; + break; + case TUI::UserInput::Delete: + //cout << "Delete" << endl; + break; + case TUI::UserInput::Shred: + //cout << "Shred" << endl; + break; + case TUI::UserInput::Enter: + //cout << "Enter" << endl; + break; + case TUI::UserInput::ESC: + //cout << "ESC" << endl; + break; + default: + break; + } + } +} void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { @@ -116,11 +154,9 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN } pvecOldDrives->clear(); - for (long unsigned int i=0; isize(); i++) { pvecOldDrives->push_back((*pvecNewDrives)[i]); } - } /** From 1b0ea97ed1508d126677a8a7121f3e3181d839a0 Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 7 Aug 2020 11:38:00 +0200 Subject: [PATCH 09/41] select drive based on arrow keys --- include/reHDD.h | 9 +- include/tui.h | 5 +- src/TUI/tui.cpp | 91 ++++++++---- src/drive.cpp | 21 +-- src/reHDD.cpp | 370 +++++++++++++++++++++++++++--------------------- src/smart.cpp | 94 ++++++------ src/wipe.cpp | 32 ++--- 7 files changed, 355 insertions(+), 267 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index d09a638..19a3aa8 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -50,18 +50,19 @@ public: private: - vector vecDrives; //stores all drive data + static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); static void filterIgnoredDrives(vector * pvecDrives); static void addSMARTData(vector * pvecDrives); static void ThreadScannDevices(); - static void ThreadUserInput(); + static void ThreadUserInput(); - void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); + static void handleArrowKey(TUI::UserInput userInput); + + static void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); - TUI *ui; diff --git a/include/tui.h b/include/tui.h index 9216a09..9b340e2 100644 --- a/include/tui.h +++ b/include/tui.h @@ -13,6 +13,7 @@ #define COLOR_AREA_STDSCR 1 #define COLOR_AREA_OVERVIEW 2 #define COLOR_AREA_ENTRY 3 +#define COLOR_AREA_ENTRY_SELECTED 4 #define COLOR_GRAY 8 @@ -28,7 +29,7 @@ public: static void initTUI(); - void updateTUI(vector * pvecDrives); + void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry); static enum UserInput readUserInput(); @@ -43,7 +44,7 @@ private: static void centerTitle(WINDOW *pwin, const char * title); static WINDOW *createOverViewWindow( int iXSize, int iYSize); static WINDOW *createDetailViewWindow( int iXSize, int iYSize, int iXStart); - static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity); + static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); }; diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index dec36f0..9140991 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -8,7 +8,8 @@ #include "../../include/reHDD.h" -TUI::TUI(void) { +TUI::TUI(void) +{ @@ -26,12 +27,15 @@ void TUI::initTUI() initscr(); raw(); keypad(stdscr,TRUE); - if(has_colors() == TRUE) { - start_color(); - } else { - printf("Your terminal does not support color\n"); - exit(1); - } + if(has_colors() == TRUE) + { + start_color(); + } + else + { + printf("Your terminal does not support color\n"); + exit(1); + } clear(); curs_set(0); noecho(); @@ -41,11 +45,18 @@ void TUI::initTUI() init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); + init_pair(COLOR_AREA_ENTRY, COLOR_BLACK, COLOR_GRAY); + init_pair(COLOR_AREA_ENTRY_SELECTED, COLOR_BLACK, COLOR_WHITE); + init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + + mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); } -void TUI::updateTUI(vector * pvecDrives) { +void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) +{ int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -63,19 +74,27 @@ void TUI::updateTUI(vector * pvecDrives) { vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) - { - string sModelFamily = it->getModelFamily(); - string sModelName = it->getModelName(); - string sCapacity = it->sCapacityToText(); + { + string sModelFamily = it->getModelFamily(); + string sModelName = it->getModelName(); + string sCapacity = it->sCapacityToText(); - WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity); - wrefresh(tmp); - } + bool bSelectedEntry = false; + + if(i32SelectedEntry == (it - pvecDrives->begin())) + { + bSelectedEntry = true; + } + + WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); + wrefresh(tmp); + } } -enum TUI::UserInput TUI::readUserInput() { - int ch = wgetch(stdscr); - switch(ch) +enum TUI::UserInput TUI::readUserInput() +{ + int ch = wgetch(stdscr); + switch(ch) { case KEY_UP: return TUI::UserInput::UpKey; @@ -106,7 +125,8 @@ enum TUI::UserInput TUI::readUserInput() { } -void TUI::centerTitle(WINDOW *pwin, const char * title) { +void TUI::centerTitle(WINDOW *pwin, const char * title) +{ int x, maxX, stringSize; getmaxyx(pwin, maxX, maxX); stringSize = 4 + strlen(title); @@ -118,10 +138,11 @@ void TUI::centerTitle(WINDOW *pwin, const char * title) { waddch(pwin, ACS_LTEE); } -WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) { +WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) +{ WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, 2, 2); - init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); box(newWindow, ACS_VLINE, ACS_HLINE); @@ -142,10 +163,11 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) { return newWindow; } -WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { +WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) +{ WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, 2, iXStart); - init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); box(newWindow, ACS_VLINE, ACS_HLINE); centerTitle(newWindow, "Selected Drive: xyz"); @@ -154,14 +176,27 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) { return newWindow; } -WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity) { +WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity, bool bSelected) +{ WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, iXStart); - init_pair(COLOR_AREA_ENTRY, COLOR_BLACK, COLOR_GRAY); - wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY)); - box(newWindow, ACS_VLINE, ACS_HLINE); - attron(COLOR_PAIR(COLOR_AREA_ENTRY)); + + + if(!bSelected) + { + // entry is NOT selected + attron(COLOR_PAIR(COLOR_AREA_ENTRY)); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY)); + } + else + { + // entry IS selected + attron(COLOR_PAIR(COLOR_AREA_ENTRY)); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY_SELECTED)); + } + + box(newWindow, ACS_VLINE, ACS_HLINE); mvwaddstr(newWindow,1, 1, sModelFamily.c_str()); mvwaddstr(newWindow,2, 1, sModelName.c_str()); diff --git a/src/drive.cpp b/src/drive.cpp index f816cda..367ba09 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -46,15 +46,18 @@ uint32_t Drive::getPowerCycles(void) return u32PowerCycles; } -string Drive::sCapacityToText() { - if(getCapacity() <= (999*1000000000UL)) { - // Less or even 999 GB --> GB - return to_string(getCapacity() / 1000000000UL) + " GB"; - } - else { - // More 999 GB --> TB - return to_string(getCapacity() / 1000000000000UL) + " TB"; - } +string Drive::sCapacityToText() +{ + if(getCapacity() <= (999*1000000000UL)) + { + // Less or even 999 GB --> GB + return to_string(getCapacity() / 1000000000UL) + " GB"; + } + else + { + // More 999 GB --> TB + return to_string(getCapacity() / 1000000000000UL) + " TB"; + } return "ERROR"; } diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 3d75f29..551f256 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -18,6 +18,11 @@ static std::mutex mxScannDrives; static vector vecNewDrives; //store found drives that are updated every 5sec +static vector vecDrives; //stores all drive data from scann thread + +TUI *ui; + +static int32_t i32SelectedEntry; static fd_set selectSet; @@ -29,8 +34,7 @@ static fd_set selectSet; reHDD::reHDD(void) { cout << "created app" << endl; - - + i32SelectedEntry = 0; } /** @@ -41,9 +45,7 @@ reHDD::reHDD(void) void reHDD::app_logic(void) { cout << "app logic" << endl; - ui = new TUI(); - ui->initTUI(); pipe(fdSearchDrives); @@ -56,107 +58,124 @@ void reHDD::app_logic(void) thread thDevices(ThreadScannDevices); //start thread that scanns for drives thread thUserInput(ThreadUserInput); //start thread that reads user input - while(1) { + while(1) + { - select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); + select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); - if( FD_ISSET(fdSearchDrives[0], &selectSet)) { - char dummy; - read (fdSearchDrives[0],&dummy,1); - mxScannDrives.lock(); - filterNewDrives(&vecDrives, &vecNewDrives); - //printDrives(&vecDrives); - //TODO update UI + if( FD_ISSET(fdSearchDrives[0], &selectSet)) + { + char dummy; + read (fdSearchDrives[0],&dummy,1); + mxScannDrives.lock(); - ui->updateTUI(&vecDrives); + filterNewDrives(&vecDrives, &vecNewDrives); //filter and copy to app logic vector + mxScannDrives.unlock(); + //printDrives(&vecDrives); + //TODO update UI - mxScannDrives.unlock(); - } - else if (FD_ISSET(fdWhipe[0], &selectSet)) { - cout << "Whipe signal" << endl; - } - } //endless loop + ui->updateTUI(&vecDrives, i32SelectedEntry); + + + } + else if (FD_ISSET(fdWhipe[0], &selectSet)) + { + cout << "Whipe signal" << endl; + //update percantage & state + //update ui + } + } //endless loop thDevices.join(); thUserInput.join(); } -void reHDD::ThreadScannDevices() { - while(true) { - // cout << "Thread" << endl; - mxScannDrives.lock(); - vecNewDrives.clear(); - searchDrives(&vecNewDrives); //search for new drives and store them in list - filterIgnoredDrives(&vecNewDrives); //filter out ignored drives - addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives - mxScannDrives.unlock(); - write (fdSearchDrives[1], "A",1); - sleep(5); //sleep 5 sec - } -} - -void reHDD::ThreadUserInput() { - while(true) { - - // cout << TUI::readUserInput() << endl; - switch (TUI::readUserInput()) +void reHDD::ThreadScannDevices() +{ + while(true) { - case TUI::UserInput::DownKey: - /* code */ - //cout << "Down" << endl; - break; - case TUI::UserInput::UpKey: - //cout << "Up" << endl; - break; - case TUI::UserInput::Undefined: - //cout << "Undefined" << endl; - break; - case TUI::UserInput::Abort: - //cout << "Abort" << endl; - break; - case TUI::UserInput::Delete: - //cout << "Delete" << endl; - break; - case TUI::UserInput::Shred: - //cout << "Shred" << endl; - break; - case TUI::UserInput::Enter: - //cout << "Enter" << endl; - break; - case TUI::UserInput::ESC: - //cout << "ESC" << endl; - break; - default: - break; + // cout << "Thread" << endl; + mxScannDrives.lock(); + vecNewDrives.clear(); + searchDrives(&vecNewDrives); //search for new drives and store them in list + filterIgnoredDrives(&vecNewDrives); //filter out ignored drives + addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives + mxScannDrives.unlock(); + write (fdSearchDrives[1], "A",1); + sleep(5); //sleep 5 sec } - } } -void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { +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(&vecDrives, i32SelectedEntry); + break; + case TUI::UserInput::UpKey: + //cout << "Up" << endl; + handleArrowKey(TUI::UserInput::UpKey); + ui->updateTUI(&vecDrives, i32SelectedEntry); + break; + case TUI::UserInput::Undefined: + //cout << "Undefined" << endl; + break; + case TUI::UserInput::Abort: + //cout << "Abort" << endl; + break; + case TUI::UserInput::Delete: + //cout << "Delete" << endl; + break; + case TUI::UserInput::Shred: + //cout << "Shred" << endl; + break; + case TUI::UserInput::Enter: + //cout << "Enter" << endl; + break; + case TUI::UserInput::ESC: + //cout << "ESC" << endl; + break; + default: + break; + } + } +} + +void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) +{ vector ::iterator itOld; //Iterator for current (old) drive list vector ::iterator itNew; //Iterator for new drive list that was created from to scann thread for (itOld = pvecOldDrives->begin(); itOld != pvecOldDrives->end(); ++itOld) - { - bool bOldDriveIsOffline = true; - for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end(); ++itNew) { - if(itOld->getSerial() == itNew->getSerial()) { - bOldDriveIsOffline = false; - // cout << "already online drive found: " << itOld->getPath() << endl; - } - } + bool bOldDriveIsOffline = true; + for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end(); ++itNew) + { + if(itOld->getSerial() == itNew->getSerial()) + { + bOldDriveIsOffline = false; + // cout << "already online drive found: " << itOld->getPath() << endl; + } + } - if(bOldDriveIsOffline == true) { - //cout << "offline drive found: " << itOld->getPath() << endl; - //TODO kill wipe thread + if(bOldDriveIsOffline == true) + { + //cout << "offline drive found: " << itOld->getPath() << endl; + //TODO kill wipe thread + } } - } pvecOldDrives->clear(); - for (long unsigned int i=0; isize(); i++) { - pvecOldDrives->push_back((*pvecNewDrives)[i]); - } + for (long unsigned int i=0; isize(); i++) + { + pvecOldDrives->push_back((*pvecNewDrives)[i]); + } } /** @@ -173,18 +192,18 @@ void reHDD::searchDrives(vector * pvecDrives) FILE* outputfileHwinfo = popen("hwinfo --short --disk", "r"); if (outputfileHwinfo == NULL) - { - exit(EXIT_FAILURE); - } + { + exit(EXIT_FAILURE); + } while ((getline(&cLine, &len, outputfileHwinfo)) != -1) - { - if (string(cLine).find("/dev/sd") != string::npos) { - Drive* tmpDrive = new Drive(string(cLine).substr (2,8)); - pvecDrives->push_back(*tmpDrive); + if (string(cLine).find("/dev/sd") != string::npos) + { + Drive* tmpDrive = new Drive(string(cLine).substr (2,8)); + pvecDrives->push_back(*tmpDrive); + } } - } fclose(outputfileHwinfo); } @@ -206,72 +225,72 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) ifstream input( "ignoreDrives.conf" ); //read ingnore file for(string sLine; getline( input, sLine );) - { - if (string(sLine).find("/dev/sd") != string::npos) { - size_t pos = 0; - string token; - while ((pos = sLine.find(sDelimiter)) != string::npos) - { - token = sLine.substr(0, pos); - sIgnoredDrivePath = token; - sLine.erase(0, pos + sDelimiter.length()); - sIgnoredDriveUUID = sLine; - } //end while - //cout << "Path: " << sIgnoredDrivePath << std::endl; - //cout << "UUID: " << sIgnoredDriveUUID << std::endl; - vtlIgnoredDevices.emplace_back(sIgnoredDrivePath, sIgnoredDriveUUID); //add found path and uuid from ingnore file to vector + if (string(sLine).find("/dev/sd") != string::npos) + { + size_t pos = 0; + string token; + while ((pos = sLine.find(sDelimiter)) != string::npos) + { + token = sLine.substr(0, pos); + sIgnoredDrivePath = token; + sLine.erase(0, pos + sDelimiter.length()); + sIgnoredDriveUUID = sLine; + } //end while + //cout << "Path: " << sIgnoredDrivePath << std::endl; + //cout << "UUID: " << sIgnoredDriveUUID << std::endl; + vtlIgnoredDevices.emplace_back(sIgnoredDrivePath, sIgnoredDriveUUID); //add found path and uuid from ingnore file to vector + } } - } //loop through found entries in ingnore file for(auto row : vtlIgnoredDevices) - { - vector ::iterator it; - for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { - string sUUID; - if (!get<0>(row).compare(it->getPath())) //find same drive based on path - { - 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) + vector ::iterator it; + for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { - exit(EXIT_FAILURE); - } + string sUUID; + if (!get<0>(row).compare(it->getPath())) //find same drive based on path + { + 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(36, sBlkidOut.length() - 36); - sUUID = sBlkidOut; - //cout << "blkid uuid:" << sUUID << endl; - } - } - fclose(outputfileBlkid); - //cout << "blkid uuid:" << sUUID << endl; + 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(36, sBlkidOut.length() - 36); + sUUID = sBlkidOut; + //cout << "blkid uuid:" << sUUID << endl; + } + } + fclose(outputfileBlkid); + //cout << "blkid uuid:" << sUUID << endl; - if (get<1>(row).compare(sUUID)) //compare uuid from ignore file and uuid from drive - { - cout << "[ERROR] different uuid found than in ignore file:" << it->getPath() << endl; - exit(EXIT_FAILURE); // exit to prevent accidentally shred a system drive + if (get<1>(row).compare(sUUID)) //compare uuid from ignore file and uuid from drive + { + cout << "[ERROR] different uuid found than in ignore file:" << it->getPath() << endl; + exit(EXIT_FAILURE); // exit to prevent accidentally shred a system drive + } + else + { + // same uuid found than in ignore file --> ignore this drive + it = pvecDrives->erase(it); + it--; + //cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; + } + } } - else - { - // same uuid found than in ignore file --> ignore this drive - it = pvecDrives->erase(it); - it--; - //cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; - } - } } - } } /** @@ -284,18 +303,18 @@ void reHDD::printDrives(vector * pvecDrives) cout << "------------DRIVES---------------" << endl; vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->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; - } + { + 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; + } cout << "---------------------------------" << endl; } @@ -308,8 +327,33 @@ void reHDD::addSMARTData(vector * pvecDrives) { vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) - { - Drive* pTmpDrive = iterator_to_pointer::iterator > (it); - SMART::readSMARTData(pTmpDrive); - } + { + Drive* pTmpDrive = iterator_to_pointer::iterator > (it); + SMART::readSMARTData(pTmpDrive); + } +} + +void reHDD::handleArrowKey(TUI::UserInput userInput) +{ + int32_t i32EntrySize = (int32_t) vecDrives.size(); + switch (userInput) + { + case TUI::UserInput::DownKey: + i32SelectedEntry++; + if(i32SelectedEntry >= i32EntrySize) + { + i32SelectedEntry = 0; + } + break; + case TUI::UserInput::UpKey: + i32SelectedEntry--; + if(i32SelectedEntry < 0) + { + i32SelectedEntry = (i32EntrySize-1); + } + break; + default: + i32SelectedEntry = 0; + break; + } } \ No newline at end of file diff --git a/src/smart.cpp b/src/smart.cpp index f85cab1..815ef00 100644 --- a/src/smart.cpp +++ b/src/smart.cpp @@ -40,17 +40,17 @@ void SMART::readSMARTData(Drive* drive) FILE* outputfileSmart = popen(cpComand, "r"); while ((getline(&cLine, &len, outputfileSmart)) != -1) - { - string sLine = string(cLine); + { + string sLine = string(cLine); - SMART::parseModelFamily(sLine); - SMART::parseModelName(sLine); - SMART::parseSerial(sLine); - SMART::parseCapacity(sLine); - SMART::parseErrorCount(sLine); - SMART::parsePowerOnHours(sLine); - SMART::parsePowerCycle(sLine); - } + SMART::parseModelFamily(sLine); + SMART::parseModelName(sLine); + SMART::parseSerial(sLine); + SMART::parseCapacity(sLine); + SMART::parseErrorCount(sLine); + SMART::parsePowerOnHours(sLine); + SMART::parsePowerCycle(sLine); + } fclose(outputfileSmart); drive->setDriveSMARTData(modelFamily, modelName, serial, capacity, errorCount, powerOnHours, powerCycle); //wirte data in drive } @@ -64,11 +64,12 @@ void SMART::parseModelFamily(string sLine) { string search("\"model_family\": "); size_t found = sLine.find(search); - if (found!=string::npos) { - sLine.erase(0, sLine.find(": ") + 3); - sLine.erase(sLine.length()-3, 3); - modelFamily = sLine; - } + if (found!=string::npos) + { + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + modelFamily = sLine; + } } /** @@ -80,11 +81,12 @@ void SMART::parseModelName(string sLine) { string search("\"model_name\": "); size_t found = sLine.find(search); - if (found!=string::npos) { - sLine.erase(0, sLine.find(": ") + 3); - sLine.erase(sLine.length()-3, 3); - modelName = sLine; - } + if (found!=string::npos) + { + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + modelName = sLine; + } } /** @@ -96,11 +98,12 @@ void SMART::parseSerial(string sLine) { string search("\"serial_number\": "); size_t found = sLine.find(search); - if (found!=string::npos) { - sLine.erase(0, sLine.find(": ") + 3); - sLine.erase(sLine.length()-3, 3); - serial = sLine; - } + if (found!=string::npos) + { + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + serial = sLine; + } } /** @@ -112,11 +115,12 @@ void SMART::parseCapacity(string sLine) { string search("\"bytes\": "); size_t found = sLine.find(search); - if (found!=string::npos) { - sLine.erase(0, sLine.find(": ") + 2); - sLine.erase(sLine.length()-1, 1); - capacity = stol(sLine); - } + if (found!=string::npos) + { + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-1, 1); + capacity = stol(sLine); + } } /** @@ -129,11 +133,11 @@ void SMART::parseErrorCount(string sLine) string search("\"error_count_total\": "); size_t found = sLine.find(search); if (found!=string::npos) - { - sLine.erase(0, sLine.find(": ")+2); - sLine.erase(sLine.length()-2, 2); - errorCount = stol(sLine); - } + { + sLine.erase(0, sLine.find(": ")+2); + sLine.erase(sLine.length()-2, 2); + errorCount = stol(sLine); + } } /** @@ -146,12 +150,12 @@ void SMART::parsePowerOnHours(string sLine) string search("\"hours\": "); size_t found = sLine.find(search); if (found!=string::npos) - { - sLine.erase(0, sLine.find(": ") + 2); - sLine.erase(sLine.length()-1, 1); - powerOnHours = stol(sLine); + { + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-1, 1); + powerOnHours = stol(sLine); - } + } } /** @@ -164,11 +168,11 @@ void SMART::parsePowerCycle(string sLine) string search("\"power_cycle_count\": "); size_t found = sLine.find(search); if (found!=string::npos) - { - sLine.erase(0, sLine.find(": ") + 2); - sLine.erase(sLine.length()-2, 2); - powerCycle = stol(sLine); + { + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-2, 2); + powerCycle = stol(sLine); - } + } } diff --git a/src/wipe.cpp b/src/wipe.cpp index 827328c..d37e5b0 100644 --- a/src/wipe.cpp +++ b/src/wipe.cpp @@ -36,10 +36,10 @@ void Wipe::wipeDrive(Drive* drive) FILE* outputfileSmart = popen(cpComand, "r"); while ((getline(&cLine, &len, outputfileSmart)) != -1) - { - string sLine = string(cLine); - cout << sLine; - } + { + string sLine = string(cLine); + cout << sLine; + } fclose(outputfileSmart); auto t_end = chrono::high_resolution_clock::now(); @@ -51,22 +51,22 @@ void Wipe::wipeDrive(Drive* drive) uint64_t u64seconds = 0U; while(u64TimeMS >= 1000) - { - u64seconds++; - u64TimeMS = u64TimeMS - 1000; - } + { + u64seconds++; + u64TimeMS = u64TimeMS - 1000; + } while(u64seconds >= 60) - { - u64minutes++; - u64seconds = u64seconds - 60; - } + { + u64minutes++; + u64seconds = u64seconds - 60; + } while(u64minutes >= 60) - { - u64hours++; - u64minutes = u64minutes - 60; - } + { + u64hours++; + u64minutes = u64minutes - 60; + } cout << "Elapsed time: " << u64hours << " h - " << u64minutes << " min - " << u64seconds << " sec" << endl; } From 99273756dafeb2e92d33b2990c62d6b18c2d1abe Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 7 Aug 2020 12:07:29 +0200 Subject: [PATCH 10/41] show drive overview --- include/drive.h | 3 +++ include/reHDD.h | 13 +++---------- include/tui.h | 2 +- src/TUI/tui.cpp | 31 ++++++++++++++++++++++++++----- src/drive.cpp | 16 ++++++++++++++++ 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/include/drive.h b/include/drive.h index 53bad36..f1377c9 100644 --- a/include/drive.h +++ b/include/drive.h @@ -38,6 +38,9 @@ public: uint32_t powerCycles); string sCapacityToText(); + string sErrorCountToText(); + string sPowerOnHoursToText(); + string sPowerCyclesToText(); private: string sPath; diff --git a/include/reHDD.h b/include/reHDD.h index 19a3aa8..0fdf6fc 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -50,21 +50,14 @@ public: private: - - static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); static void filterIgnoredDrives(vector * pvecDrives); static void addSMARTData(vector * pvecDrives); static void ThreadScannDevices(); - static void ThreadUserInput(); - - static void handleArrowKey(TUI::UserInput userInput); - - static void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); - - - + static void ThreadUserInput(); + static void handleArrowKey(TUI::UserInput userInput); + static void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); }; diff --git a/include/tui.h b/include/tui.h index 9b340e2..0bbd795 100644 --- a/include/tui.h +++ b/include/tui.h @@ -43,7 +43,7 @@ private: static void centerTitle(WINDOW *pwin, const char * title); static WINDOW *createOverViewWindow( int iXSize, int iYSize); - static WINDOW *createDetailViewWindow( int iXSize, int iYSize, int iXStart); + static WINDOW *createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive drive); static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); }; diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 9140991..c92872e 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -69,9 +69,6 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) overview=createOverViewWindow((int)(stdscrX/3), (stdscrY-15)); wrefresh(overview); - detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5); - wrefresh(detailview); - vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { @@ -84,6 +81,8 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) if(i32SelectedEntry == (it - pvecDrives->begin())) { bSelectedEntry = true; + detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); + wrefresh(detailview); } WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); @@ -163,14 +162,36 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) return newWindow; } -WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart) +WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive drive) { WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, 2, iXStart); wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); box(newWindow, ACS_VLINE, ACS_HLINE); - centerTitle(newWindow, "Selected Drive: xyz"); + string title = "Selected Drive: " + drive.getModelName() + " " + drive.sCapacityToText(); + centerTitle(newWindow, title.c_str()); + + + + + string sPath = "Path: " +drive.getPath(); + string sModelFamlily = "ModelFamily: " + drive.getModelFamily(); + string sModelName = "ModelName: " + drive.getModelName(); + string sCapacity = "Capacity: " + drive.sCapacityToText(); + string sSerial = "Serial: " + drive.getSerial(); + string sPowerOnHours = "PowerOnHours: " + drive.sPowerOnHoursToText(); + string sPowerCycle = "PowerCycle: " + drive.sPowerCyclesToText(); + string sErrorCount = "ErrorCount: " + drive.sErrorCountToText(); + + mvwaddstr(newWindow,2, 3, sPath.c_str()); + mvwaddstr(newWindow,3, 3, sModelFamlily.c_str()); + mvwaddstr(newWindow,4, 3, sModelName.c_str()); + mvwaddstr(newWindow,5, 3, sCapacity.c_str()); + mvwaddstr(newWindow,6, 3, sSerial.c_str()); + mvwaddstr(newWindow,7, 3, sPowerOnHours.c_str()); + mvwaddstr(newWindow,8, 3, sPowerCycle.c_str()); + mvwaddstr(newWindow,9, 3, sErrorCount.c_str()); keypad(newWindow, TRUE); return newWindow; diff --git a/src/drive.cpp b/src/drive.cpp index 367ba09..67b28f8 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -61,6 +61,22 @@ string Drive::sCapacityToText() return "ERROR"; } + string Drive::sErrorCountToText(){ + return to_string(getErrorCount()); + } + + + string Drive::sPowerOnHoursToText(){ + + //TODO show in human readable format + + return to_string(getPowerOnHours()); + + } + string Drive::sPowerCyclesToText(){ + return to_string(getPowerCycles()); + } + /** * \brief set S.M.A.R.T. values in model * \param string modelFamily From 29eda094a73c9df71bf78e29e0e496a82161e283 Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 7 Aug 2020 13:17:52 +0200 Subject: [PATCH 11/41] show drive stats human readable --- include/reHDD.h | 2 ++ src/drive.cpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 0fdf6fc..df32002 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include using namespace std; diff --git a/src/drive.cpp b/src/drive.cpp index 67b28f8..cb30cca 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -67,12 +67,18 @@ string Drive::sCapacityToText() string Drive::sPowerOnHoursToText(){ + double dYears = 0U; + uint32_t u32Hours = getPowerOnHours(); + stringstream stream; - //TODO show in human readable format - - return to_string(getPowerOnHours()); + dYears = (double) ((double)u32Hours/(double)8760U); + + stream << fixed << setprecision(2) << dYears; + string sRet = to_string(getPowerOnHours()) + " hours or " + stream.str() + " years"; + return sRet; } + string Drive::sPowerCyclesToText(){ return to_string(getPowerCycles()); } From 1e4d9975c60d78c703a8dac1d05b667c3748296b Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 7 Aug 2020 16:17:45 +0200 Subject: [PATCH 12/41] added warning levels --- include/reHDD.h | 3 ++ include/tui.h | 3 +- src/TUI/tui.cpp | 83 ++++++++++++++++++++++++++++++++----------------- src/drive.cpp | 35 +++++++++++---------- 4 files changed, 78 insertions(+), 46 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index df32002..7ac6bfd 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -10,6 +10,9 @@ #define DRYRUN +#define WORSE_HOURS 19200 //mark drive if at this limit or beyond +#define WORSE_POWERUP 4000 //mark drive if at this limit or beyond + #include #include #include diff --git a/include/tui.h b/include/tui.h index 0bbd795..7748ef5 100644 --- a/include/tui.h +++ b/include/tui.h @@ -14,8 +14,7 @@ #define COLOR_AREA_OVERVIEW 2 #define COLOR_AREA_ENTRY 3 #define COLOR_AREA_ENTRY_SELECTED 4 - -#define COLOR_GRAY 8 +#define COLOR_AREA_DETAIL 5 class TUI { diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index c92872e..504d520 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -40,16 +40,13 @@ void TUI::initTUI() curs_set(0); noecho(); cbreak(); - init_color(COLOR_GRAY, 173, 170, 173); - init_pair(COLOR_AREA_STDSCR,COLOR_WHITE, COLOR_BLUE); wbkgd(stdscr, COLOR_PAIR(COLOR_AREA_STDSCR)); - init_pair(COLOR_AREA_ENTRY, COLOR_BLACK, COLOR_GRAY); - init_pair(COLOR_AREA_ENTRY_SELECTED, COLOR_BLACK, COLOR_WHITE); - init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); - init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_GRAY); - + init_pair(COLOR_AREA_ENTRY, COLOR_BLACK, COLOR_WHITE); + init_pair(COLOR_AREA_ENTRY_SELECTED, COLOR_BLACK, COLOR_RED); + init_pair(COLOR_AREA_OVERVIEW, COLOR_BLACK, COLOR_WHITE); + init_pair(COLOR_AREA_DETAIL, COLOR_BLACK, COLOR_WHITE); mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); @@ -81,8 +78,8 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) if(i32SelectedEntry == (it - pvecDrives->begin())) { bSelectedEntry = true; - detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); - wrefresh(detailview); + detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); + wrefresh(detailview); } WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); @@ -166,33 +163,63 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive { WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, 2, iXStart); - - wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_DETAIL)); box(newWindow, ACS_VLINE, ACS_HLINE); string title = "Selected Drive: " + drive.getModelName() + " " + drive.sCapacityToText(); centerTitle(newWindow, title.c_str()); + string sPath = "Path: " +drive.getPath(); + string sModelFamlily = "ModelFamily: " + drive.getModelFamily(); + string sModelName = "ModelName: " + drive.getModelName(); + string sCapacity = "Capacity: " + drive.sCapacityToText(); + string sSerial = "Serial: " + drive.getSerial(); + string sPowerOnHours = "PowerOnHours: " + drive.sPowerOnHoursToText(); + string sPowerCycle = "PowerCycle: " + drive.sPowerCyclesToText(); + string sErrorCount = "ErrorCount: " + drive.sErrorCountToText(); - + uint16_t u16Line = 2; - string sPath = "Path: " +drive.getPath(); - string sModelFamlily = "ModelFamily: " + drive.getModelFamily(); - string sModelName = "ModelName: " + drive.getModelName(); - string sCapacity = "Capacity: " + drive.sCapacityToText(); - string sSerial = "Serial: " + drive.getSerial(); - string sPowerOnHours = "PowerOnHours: " + drive.sPowerOnHoursToText(); - string sPowerCycle = "PowerCycle: " + drive.sPowerCyclesToText(); - string sErrorCount = "ErrorCount: " + drive.sErrorCountToText(); + mvwaddstr(newWindow,u16Line++, 3, sPath.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sModelFamlily.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sModelName.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sCapacity.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sSerial.c_str()); - mvwaddstr(newWindow,2, 3, sPath.c_str()); - mvwaddstr(newWindow,3, 3, sModelFamlily.c_str()); - mvwaddstr(newWindow,4, 3, sModelName.c_str()); - mvwaddstr(newWindow,5, 3, sCapacity.c_str()); - mvwaddstr(newWindow,6, 3, sSerial.c_str()); - mvwaddstr(newWindow,7, 3, sPowerOnHours.c_str()); - mvwaddstr(newWindow,8, 3, sPowerCycle.c_str()); - mvwaddstr(newWindow,9, 3, sErrorCount.c_str()); + attroff(COLOR_PAIR(COLOR_AREA_DETAIL)); + if(drive.getPowerOnHours() >= WORSE_HOURS) + { + mvwaddstr(newWindow,u16Line++, 3, "------------> WARNING: OPERATING HOURS <-----------"); + mvwaddstr(newWindow,u16Line++, 3, sPowerOnHours.c_str()); + mvwaddstr(newWindow,u16Line++, 3, "---------------------------------------------------"); + } + else + { + mvwaddstr(newWindow,u16Line++, 3, sPowerOnHours.c_str()); + } + + if(drive.getPowerCycles() >= WORSE_POWERUP) + { + mvwaddstr(newWindow,u16Line++, 3, "------------> WARNING: POWER-ON <------------------"); + mvwaddstr(newWindow,u16Line++, 3, sPowerCycle.c_str()); + mvwaddstr(newWindow,u16Line++, 3, "---------------------------------------------------"); + } + else + { + mvwaddstr(newWindow,u16Line++, 3, sPowerCycle.c_str()); + } + + if(drive.getErrorCount() > 0) + { + mvwaddstr(newWindow,u16Line++, 3, "------------> WARNING: S.M.A.R.T ERROR <-----------"); + mvwaddstr(newWindow,u16Line++, 3, sErrorCount.c_str()); + mvwaddstr(newWindow,u16Line++, 3, "---------------------------------------------------"); + } + else + { + mvwaddstr(newWindow,u16Line++, 3, sErrorCount.c_str()); + } + keypad(newWindow, TRUE); return newWindow; } diff --git a/src/drive.cpp b/src/drive.cpp index cb30cca..1b7b5c3 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -61,27 +61,30 @@ string Drive::sCapacityToText() return "ERROR"; } - string Drive::sErrorCountToText(){ - return to_string(getErrorCount()); - } +string Drive::sErrorCountToText() +{ + return to_string(getErrorCount()); +} - string Drive::sPowerOnHoursToText(){ - double dYears = 0U; - uint32_t u32Hours = getPowerOnHours(); - stringstream stream; +string Drive::sPowerOnHoursToText() +{ + double dYears = 0U; + uint32_t u32Hours = getPowerOnHours(); + stringstream stream; - dYears = (double) ((double)u32Hours/(double)8760U); - - stream << fixed << setprecision(2) << dYears; - string sRet = to_string(getPowerOnHours()) + " hours or " + stream.str() + " years"; + dYears = (double) ((double)u32Hours/(double)8760U); - return sRet; - } + stream << fixed << setprecision(2) << dYears; + string sRet = to_string(getPowerOnHours()) + " hours or " + stream.str() + " years"; - string Drive::sPowerCyclesToText(){ - return to_string(getPowerCycles()); - } + return sRet; +} + +string Drive::sPowerCyclesToText() +{ + return to_string(getPowerCycles()); +} /** * \brief set S.M.A.R.T. values in model From f4726bf08c1038b3c99106670064807721ed8be5 Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 7 Aug 2020 22:52:11 +0200 Subject: [PATCH 13/41] added system view with time --- include/tui.h | 3 +++ src/TUI/tui.cpp | 44 ++++++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/include/tui.h b/include/tui.h index 7748ef5..99a1e4c 100644 --- a/include/tui.h +++ b/include/tui.h @@ -39,11 +39,14 @@ private: WINDOW *detailview; WINDOW *overview; + WINDOW *systemview; + 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 *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); + static WINDOW *createSystemStats(int iXSize, int iYSize, int iYStart); }; diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 504d520..2db6682 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -54,7 +54,6 @@ void TUI::initTUI() void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) { - int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -66,6 +65,9 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) overview=createOverViewWindow((int)(stdscrX/3), (stdscrY-15)); wrefresh(overview); + systemview=createSystemStats((int)(stdscrX/3), 10, (stdscrY-11)); + wrefresh(systemview); + vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { @@ -120,7 +122,6 @@ enum TUI::UserInput TUI::readUserInput() return TUI::UserInput::Undefined; } - void TUI::centerTitle(WINDOW *pwin, const char * title) { int x, maxX, stringSize; @@ -142,18 +143,7 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); box(newWindow, ACS_VLINE, ACS_HLINE); - time_t rawtime; - struct tm * timeinfo; - char buffer[80]; - - time (&rawtime); - timeinfo = localtime(&rawtime); - - strftime(buffer,sizeof(buffer),"%d-%m-%Y %H:%M:%S",timeinfo); - std::string str(buffer); - - - centerTitle(newWindow, str.c_str()); + centerTitle(newWindow, "Detected Drives"); keypad(newWindow, TRUE); return newWindow; @@ -229,8 +219,6 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, iXStart); - - if(!bSelected) { // entry is NOT selected @@ -254,3 +242,27 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, return newWindow; } + +WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart){ + +WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, iYStart, 2); + + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + centerTitle(newWindow, "System"); + + time_t rawtime; + struct tm * timeinfo; + char buffer[80]; + time (&rawtime); + timeinfo = localtime(&rawtime); + strftime(buffer,sizeof(buffer),"Date: %d-%m-%Y Time: %H:%M",timeinfo); + string time(buffer); + + mvwaddstr(newWindow,2, 2, time.c_str()); + + keypad(newWindow, TRUE); + return newWindow; +} From c2ab7c1c7f9e82c0d6f8428b8e66d2b25335e7a8 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 9 Aug 2020 21:41:28 +0200 Subject: [PATCH 14/41] added states for tasks --- include/drive.h | 38 ++++++++++++++------ include/reHDD.h | 8 ++++- include/tui.h | 21 +++++++---- src/TUI/tui.cpp | 60 +++++++++++++++++++++++++++---- src/drive.cpp | 13 +++++++ src/reHDD.cpp | 95 +++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 203 insertions(+), 32 deletions(-) diff --git a/include/drive.h b/include/drive.h index f1377c9..b7b1423 100644 --- a/include/drive.h +++ b/include/drive.h @@ -12,6 +12,29 @@ class Drive { + +public: + enum TaskState {NONE, + SHRED_SELECTED, + SHRED_ACTIVE, + SHRED_FINISHED, + DELETE_SELECTED, + DELETE_ACTIVE, + DELETE_FINISHED + } state; + +private: + string sPath; + string sModelFamily; + string sModelName; + string sSerial; + uint64_t u64Capacity = 0U; //in byte + uint32_t u32ErrorCount = 0U; + uint32_t u32PowerOnHours = 0U; //in hours + uint32_t u32PowerCycles = 0U; + + uint8_t u8TaskPercentage = 0U; //in percent for Shred (1 to 100) and Delete (1 OR 100) + protected: public: @@ -42,18 +65,11 @@ public: string sPowerOnHoursToText(); string sPowerCyclesToText(); + void setTaskPercentage(uint8_t u8TaskPercentage); + uint8_t getTaskPercentage(void); + + private: - string sPath; - string sModelFamily; - string sModelName; - string sSerial; - uint64_t u64Capacity = 0U; //in byte - uint32_t u32ErrorCount = 0U; - uint32_t u32PowerOnHours = 0U; //in hours - uint32_t u32PowerCycles = 0U; - uint32_t u32ShredPercentage = 0U; //in percent - - }; diff --git a/include/reHDD.h b/include/reHDD.h index 7ac6bfd..81aabc6 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -13,6 +13,8 @@ #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 4000 //mark drive if at this limit or beyond +#define SELECTED_DRIVE vecDrives.at(i32SelectedEntry) + #include #include #include @@ -58,11 +60,15 @@ private: static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); static void filterIgnoredDrives(vector * pvecDrives); + static void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); static void addSMARTData(vector * pvecDrives); static void ThreadScannDevices(); static void ThreadUserInput(); static void handleArrowKey(TUI::UserInput userInput); - static void filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives); + static void handleEnter(); + static void handleESC(); + static void handleAbort(); + }; diff --git a/include/tui.h b/include/tui.h index 99a1e4c..5092ada 100644 --- a/include/tui.h +++ b/include/tui.h @@ -23,12 +23,21 @@ protected: public: enum UserInput { UpKey, DownKey, Abort, Shred, Delete, Enter, ESC, Undefined}; + struct MenuState + { + bool bAbort; + bool bShred; + bool bDelete; + bool bConfirmAbort; + bool bConfirmShred; + bool bConfirmDelete; + }; TUI(void); static void initTUI(); - void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry); + void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct MenuState menustate); static enum UserInput readUserInput(); @@ -37,17 +46,17 @@ private: static string sRamUsage; static string sLocalTime; - WINDOW *detailview; - WINDOW *overview; - WINDOW *systemview; - + WINDOW* detailview; + WINDOW* overview; + WINDOW* systemview; + WINDOW* menuview; 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 *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); static WINDOW *createSystemStats(int iXSize, int iYSize, int iYStart); - + static WINDOW *createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate); }; #endif // TUI_H_ \ No newline at end of file diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 2db6682..0d8d52d 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -49,10 +49,9 @@ void TUI::initTUI() init_pair(COLOR_AREA_DETAIL, COLOR_BLACK, COLOR_WHITE); mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); - } -void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) +void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct MenuState menustate) { int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -68,6 +67,9 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) systemview=createSystemStats((int)(stdscrX/3), 10, (stdscrY-11)); wrefresh(systemview); + menuview=createMenuView(((stdscrX)-(int)(stdscrX/3)-7), 10, (int)(stdscrX/3)+5,(stdscrY-11), menustate); + wrefresh(menuview); + vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { @@ -80,7 +82,7 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) if(i32SelectedEntry == (it - pvecDrives->begin())) { bSelectedEntry = true; - detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-3), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); + detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); wrefresh(detailview); } @@ -209,7 +211,7 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive { mvwaddstr(newWindow,u16Line++, 3, sErrorCount.c_str()); } - + keypad(newWindow, TRUE); return newWindow; } @@ -243,9 +245,10 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, return newWindow; } -WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart){ +WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart) +{ -WINDOW *newWindow; + WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, 2); wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); @@ -262,7 +265,50 @@ WINDOW *newWindow; string time(buffer); mvwaddstr(newWindow,2, 2, time.c_str()); - + + keypad(newWindow, TRUE); + return newWindow; +} + +WINDOW* TUI::createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate) +{ + + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, iYStart, iXStart); + + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_OVERVIEW)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + centerTitle(newWindow, "Controls"); + + + uint16_t u16Line = 2; + + if(menustate.bAbort) + { + mvwaddstr(newWindow,u16Line++, 3, "Press A for Abort"); + } + if(menustate.bShred) + { + mvwaddstr(newWindow,u16Line++, 3, "Press S for Shred"); + } + if(menustate.bDelete) + { + mvwaddstr(newWindow,u16Line++, 3, "Press D for Delete"); + } + if (menustate.bConfirmAbort) + { + mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Abort or ESC to return"); + } + if (menustate.bConfirmShred) + { + mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Shred or ESC to return"); + } + if (menustate.bConfirmDelete) + { + mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Delete or ESC to return"); + } + keypad(newWindow, TRUE); return newWindow; } diff --git a/src/drive.cpp b/src/drive.cpp index 1b7b5c3..e078325 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -86,6 +86,19 @@ string Drive::sPowerCyclesToText() return to_string(getPowerCycles()); } +void Drive::setTaskPercentage(uint8_t u8TaskPercentage) +{ + if((u8TaskPercentage >= 0) && (u8TaskPercentage <= 100)) + { + this->u8TaskPercentage = u8TaskPercentage; + } +} +uint8_t Drive::getTaskPercentage(void) +{ + return this->u8TaskPercentage; +} + + /** * \brief set S.M.A.R.T. values in model * \param string modelFamily diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 551f256..292183e 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -26,6 +26,8 @@ static int32_t i32SelectedEntry; static fd_set selectSet; +static struct TUI::MenuState menustate; + /** * \brief app constructor * \param void @@ -60,7 +62,6 @@ void reHDD::app_logic(void) while(1) { - select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); if( FD_ISSET(fdSearchDrives[0], &selectSet)) @@ -74,9 +75,7 @@ void reHDD::app_logic(void) //printDrives(&vecDrives); //TODO update UI - ui->updateTUI(&vecDrives, i32SelectedEntry); - - + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); } else if (FD_ISSET(fdWhipe[0], &selectSet)) { @@ -115,27 +114,45 @@ void reHDD::ThreadUserInput() case TUI::UserInput::DownKey: //cout << "Down" << endl; handleArrowKey(TUI::UserInput::DownKey); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::UpKey: //cout << "Up" << endl; handleArrowKey(TUI::UserInput::UpKey); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::Undefined: //cout << "Undefined" << endl; break; case TUI::UserInput::Abort: //cout << "Abort" << endl; + + handleAbort(); + break; case TUI::UserInput::Delete: //cout << "Delete" << endl; + + if(SELECTED_DRIVE.state == Drive::NONE) + { + + SELECTED_DRIVE.state = Drive::DELETE_SELECTED; + } + break; case TUI::UserInput::Shred: //cout << "Shred" << endl; + + if(SELECTED_DRIVE.state == Drive::NONE) + { + + SELECTED_DRIVE.state = Drive::SHRED_SELECTED; + } + break; case TUI::UserInput::Enter: //cout << "Enter" << endl; + handleEnter(); break; case TUI::UserInput::ESC: //cout << "ESC" << endl; @@ -161,13 +178,15 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN { bOldDriveIsOffline = false; // cout << "already online drive found: " << itOld->getPath() << endl; + itNew->state = itOld->state; + itNew->setTaskPercentage(itOld->getTaskPercentage()); } } if(bOldDriveIsOffline == true) { //cout << "offline drive found: " << itOld->getPath() << endl; - //TODO kill wipe thread + //TODO kill task thread if running } } @@ -356,4 +375,66 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) i32SelectedEntry = 0; break; } + + + if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::TaskState::DELETE_ACTIVE) + { + //task for drive is running --> don´t show more task options + menustate.bAbort = true; //activate abort + menustate.bConfirmAbort = false; + menustate.bDelete = false; + menustate.bShred = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; + + } + else + { + //no task for drive is running --> show more task options + menustate.bAbort = false; //deactivate abort + menustate.bConfirmAbort = false; + menustate.bDelete = true; + menustate.bShred = true; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; + + } +} + +void reHDD::handleEnter() +{ + if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) + { + SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; + //TODO start shredding + } + + if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) + { + SELECTED_DRIVE.state = Drive::TaskState::DELETE_ACTIVE; + //TODO start deleting + } +} + + +void reHDD::handleESC() +{ + if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) + { + SELECTED_DRIVE.state = Drive::TaskState::NONE; + } + + if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) + { + SELECTED_DRIVE.state = Drive::TaskState::NONE; + } +} + +void reHDD::handleAbort() +{ + if(SELECTED_DRIVE.state == Drive::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::DELETE_ACTIVE ) + { + // TODO cancle shred or delete + SELECTED_DRIVE.state = Drive::NONE; + } } \ No newline at end of file From cf2bb664d71366d16bfacf0fd88a6ac76a286e32 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 9 Aug 2020 23:05:32 +0200 Subject: [PATCH 15/41] fully implemented user menu --- include/tui.h | 5 +++-- src/TUI/tui.cpp | 45 +++++++++++++++++++++++++++---------- src/reHDD.cpp | 59 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/include/tui.h b/include/tui.h index 5092ada..fcc6b9a 100644 --- a/include/tui.h +++ b/include/tui.h @@ -28,7 +28,6 @@ public: bool bAbort; bool bShred; bool bDelete; - bool bConfirmAbort; bool bConfirmShred; bool bConfirmDelete; }; @@ -50,6 +49,7 @@ private: WINDOW* overview; WINDOW* systemview; WINDOW* menuview; + WINDOW* dialog; static void centerTitle(WINDOW *pwin, const char * title); static WINDOW *createOverViewWindow( int iXSize, int iYSize); @@ -57,6 +57,7 @@ private: static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); static WINDOW *createSystemStats(int iXSize, int iYSize, 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, string selectedTask, string optionA, string optionB); +}; #endif // TUI_H_ \ No newline at end of file diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index 0d8d52d..ad9b9c5 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -70,6 +70,7 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct menuview=createMenuView(((stdscrX)-(int)(stdscrX/3)-7), 10, (int)(stdscrX/3)+5,(stdscrY-11), menustate); wrefresh(menuview); + vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { @@ -84,6 +85,21 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct bSelectedEntry = true; detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); wrefresh(detailview); + + if(menustate.bConfirmShred == true) + { + dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm SHRED", "Press ENTER for SHRED" , "Press ESC for cancel"); + wrefresh(dialog); + } + else if(menustate.bConfirmDelete == true) + { + dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm DELETE", "Press ENTER for DELETE" , "Press ESC for cancel"); + wrefresh(dialog); + } + else + { + delwin(dialog); + } } WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); @@ -296,18 +312,23 @@ WINDOW* TUI::createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, st { mvwaddstr(newWindow,u16Line++, 3, "Press D for Delete"); } - if (menustate.bConfirmAbort) - { - mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Abort or ESC to return"); - } - if (menustate.bConfirmShred) - { - mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Shred or ESC to return"); - } - if (menustate.bConfirmDelete) - { - mvwaddstr(newWindow,u16Line++, 3, "Press Enter to confirm Delete or ESC to return"); - } + keypad(newWindow, TRUE); + return newWindow; +} + +WINDOW* TUI::createDialog(int iXSize, int iYSize, int iXStart, int iYStart, string task, string optionA, string optionB) +{ + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, iYStart, iXStart); + + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY_SELECTED)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + centerTitle(newWindow, task.c_str()); + + uint16_t u16Line = 2; + mvwaddstr(newWindow,u16Line++, 3, optionA.c_str()); + mvwaddstr(newWindow,u16Line++, 3, optionB.c_str()); keypad(newWindow, TRUE); return newWindow; diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 292183e..46348d6 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -37,6 +37,8 @@ reHDD::reHDD(void) { cout << "created app" << endl; i32SelectedEntry = 0; + menustate.bDelete = true; + menustate.bShred = true; } /** @@ -126,36 +128,44 @@ void reHDD::ThreadUserInput() break; case TUI::UserInput::Abort: //cout << "Abort" << endl; - handleAbort(); - + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::Delete: //cout << "Delete" << endl; - if(SELECTED_DRIVE.state == Drive::NONE) { - SELECTED_DRIVE.state = Drive::DELETE_SELECTED; + menustate.bAbort = false; + menustate.bDelete = false; + menustate.bShred = false; + menustate.bConfirmDelete = true; + menustate.bConfirmShred = false; } - + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::Shred: //cout << "Shred" << endl; - if(SELECTED_DRIVE.state == Drive::NONE) { - SELECTED_DRIVE.state = Drive::SHRED_SELECTED; + menustate.bAbort = false; + menustate.bDelete = false; + menustate.bShred = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = true; } - + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::Enter: //cout << "Enter" << endl; handleEnter(); + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; case TUI::UserInput::ESC: //cout << "ESC" << endl; + handleESC(); + ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); break; default: break; @@ -376,12 +386,10 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) break; } - if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::TaskState::DELETE_ACTIVE) { //task for drive is running --> don´t show more task options menustate.bAbort = true; //activate abort - menustate.bConfirmAbort = false; menustate.bDelete = false; menustate.bShred = false; menustate.bConfirmDelete = false; @@ -392,7 +400,6 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) { //no task for drive is running --> show more task options menustate.bAbort = false; //deactivate abort - menustate.bConfirmAbort = false; menustate.bDelete = true; menustate.bShred = true; menustate.bConfirmDelete = false; @@ -406,12 +413,24 @@ void reHDD::handleEnter() if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; + //task for drive is running --> don´t show more task options + menustate.bAbort = true; //activate abort + menustate.bDelete = false; + menustate.bShred = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; //TODO start shredding } if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::DELETE_ACTIVE; + //task for drive is running --> don´t show more task options + menustate.bAbort = true; //activate abort + menustate.bDelete = false; + menustate.bShred = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; //TODO start deleting } } @@ -422,11 +441,23 @@ void reHDD::handleESC() if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::NONE; + //task for drive is selected --> remove selection + menustate.bAbort = false; //activate abort + menustate.bDelete = true; + menustate.bShred = true; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; } if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::NONE; + //task for drive is selected --> remove selection + menustate.bAbort = false; //activate abort + menustate.bDelete = true; + menustate.bShred = true; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; } } @@ -436,5 +467,11 @@ void reHDD::handleAbort() { // TODO cancle shred or delete SELECTED_DRIVE.state = Drive::NONE; + //task for drive is running --> remove selection + menustate.bAbort = false; //activate abort + menustate.bDelete = true; + menustate.bShred = true; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; } } \ No newline at end of file From dc5e6b968bcd42c652f226aa585c262f3790409b Mon Sep 17 00:00:00 2001 From: localhorst Date: Mon, 10 Aug 2020 22:52:13 +0200 Subject: [PATCH 16/41] refactor menustatr --- include/reHDD.h | 2 -- include/tui.h | 7 ++-- src/TUI/tui.cpp | 86 ++++++++++++++++++++++++++++++++++++------------- src/drive.cpp | 2 +- src/reHDD.cpp | 80 ++++++--------------------------------------- 5 files changed, 78 insertions(+), 99 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 81aabc6..e527eee 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -32,7 +32,6 @@ #include #include - using namespace std; #include "drive.h" @@ -46,7 +45,6 @@ template T* iterator_to_pointer(I i) return (&(*i)); } - class reHDD { protected: diff --git a/include/tui.h b/include/tui.h index fcc6b9a..ab80348 100644 --- a/include/tui.h +++ b/include/tui.h @@ -36,7 +36,7 @@ public: static void initTUI(); - void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct MenuState menustate); + void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry); static enum UserInput readUserInput(); @@ -45,9 +45,10 @@ private: static string sRamUsage; static string sLocalTime; - WINDOW* detailview; + WINDOW* overview; WINDOW* systemview; + WINDOW* detailview; WINDOW* menuview; WINDOW* dialog; @@ -58,6 +59,8 @@ private: static WINDOW *createSystemStats(int iXSize, int iYSize, 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, string selectedTask, string optionA, string optionB); + + void displaySelectedDrive(Drive drive, int stdscrX, int stdscrY); }; #endif // TUI_H_ \ No newline at end of file diff --git a/src/TUI/tui.cpp b/src/TUI/tui.cpp index ad9b9c5..4d3b7b8 100644 --- a/src/TUI/tui.cpp +++ b/src/TUI/tui.cpp @@ -51,7 +51,7 @@ void TUI::initTUI() mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); } -void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct MenuState menustate) +void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) { int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -67,10 +67,6 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct systemview=createSystemStats((int)(stdscrX/3), 10, (stdscrY-11)); wrefresh(systemview); - menuview=createMenuView(((stdscrX)-(int)(stdscrX/3)-7), 10, (int)(stdscrX/3)+5,(stdscrY-11), menustate); - wrefresh(menuview); - - vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { @@ -82,24 +78,8 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry, struct if(i32SelectedEntry == (it - pvecDrives->begin())) { - bSelectedEntry = true; - detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5, pvecDrives->at((it - pvecDrives->begin()))); - wrefresh(detailview); - - if(menustate.bConfirmShred == true) - { - dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm SHRED", "Press ENTER for SHRED" , "Press ESC for cancel"); - wrefresh(dialog); - } - else if(menustate.bConfirmDelete == true) - { - dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm DELETE", "Press ENTER for DELETE" , "Press ESC for cancel"); - wrefresh(dialog); - } - else - { - delwin(dialog); - } + bSelectedEntry = true; //mark this drive in entries list + displaySelectedDrive(pvecDrives->at(i32SelectedEntry), stdscrX, stdscrY); } WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); @@ -333,3 +313,63 @@ WINDOW* TUI::createDialog(int iXSize, int iYSize, int iXStart, int iYStart, stri keypad(newWindow, TRUE); return newWindow; } + +void TUI::displaySelectedDrive(Drive drive, int stdscrX, int stdscrY) +{ + struct MenuState menustate; + menustate.bAbort = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; + menustate.bDelete = false; + menustate.bShred = false; + + // set menustate based on drive state + switch (drive.state) + { + case Drive::NONE: //no task running or selected for this drive + menustate.bShred = true; + menustate.bDelete = true; + break; + case Drive::DELETE_ACTIVE : //delete task running for this drive + menustate.bAbort = true; + break; + + case Drive::SHRED_ACTIVE : //shred task running for this drive + menustate.bAbort = true; + break; + + case Drive::DELETE_SELECTED : //delete task selected for this drive + menustate.bConfirmDelete = true; + break; + + case Drive::SHRED_SELECTED : //shred task selected for this drive + menustate.bConfirmShred = true; + break; + case Drive::DELETE_FINISHED : //delete task finished for this drive + case Drive::SHRED_FINISHED : //shred task finished for this drive + break; + default: + break; + } + + detailview=createDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5, drive); + wrefresh(detailview); + + menuview=createMenuView(((stdscrX)-(int)(stdscrX/3)-7), 10, (int)(stdscrX/3)+5,(stdscrY-11), menustate); + wrefresh(menuview); + + if(menustate.bConfirmShred == true) + { + dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm SHRED", "Press ENTER for SHRED", "Press ESC for cancel"); + wrefresh(dialog); + } + else if(menustate.bConfirmDelete == true) + { + dialog=createDialog(70, 10, ((stdscrX)-(int)(stdscrX/3)-7)-(int)((stdscrX/3)+5)/2,(int)(stdscrY/2)-5, "Confirm DELETE", "Press ENTER for DELETE", "Press ESC for cancel"); + wrefresh(dialog); + } + else + { + delwin(dialog); + } +} \ No newline at end of file diff --git a/src/drive.cpp b/src/drive.cpp index e078325..ebea0e8 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -88,7 +88,7 @@ string Drive::sPowerCyclesToText() void Drive::setTaskPercentage(uint8_t u8TaskPercentage) { - if((u8TaskPercentage >= 0) && (u8TaskPercentage <= 100)) + if(u8TaskPercentage <= 100) { this->u8TaskPercentage = u8TaskPercentage; } diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 46348d6..b6d03c4 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -10,8 +10,6 @@ static int fdSearchDrives[2];//File descriptor for pipe that informs if new drives are found -static int fdUserInput[2];//File descriptor for pipe that informs if a user input occoures - static int fdWhipe[2];//File descriptor for pipe that informs if a wipe thread signals static std::mutex mxScannDrives; @@ -26,7 +24,7 @@ static int32_t i32SelectedEntry; static fd_set selectSet; -static struct TUI::MenuState menustate; +//static struct TUI::MenuState menustate; /** * \brief app constructor @@ -37,8 +35,6 @@ reHDD::reHDD(void) { cout << "created app" << endl; i32SelectedEntry = 0; - menustate.bDelete = true; - menustate.bShred = true; } /** @@ -74,10 +70,9 @@ void reHDD::app_logic(void) filterNewDrives(&vecDrives, &vecNewDrives); //filter and copy to app logic vector mxScannDrives.unlock(); - //printDrives(&vecDrives); //TODO update UI - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); } else if (FD_ISSET(fdWhipe[0], &selectSet)) { @@ -116,12 +111,12 @@ void reHDD::ThreadUserInput() case TUI::UserInput::DownKey: //cout << "Down" << endl; handleArrowKey(TUI::UserInput::DownKey); - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::UpKey: //cout << "Up" << endl; handleArrowKey(TUI::UserInput::UpKey); - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::Undefined: //cout << "Undefined" << endl; @@ -129,43 +124,33 @@ void reHDD::ThreadUserInput() case TUI::UserInput::Abort: //cout << "Abort" << endl; handleAbort(); - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::Delete: //cout << "Delete" << endl; if(SELECTED_DRIVE.state == Drive::NONE) { SELECTED_DRIVE.state = Drive::DELETE_SELECTED; - menustate.bAbort = false; - menustate.bDelete = false; - menustate.bShred = false; - menustate.bConfirmDelete = true; - menustate.bConfirmShred = false; } - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::Shred: //cout << "Shred" << endl; if(SELECTED_DRIVE.state == Drive::NONE) { SELECTED_DRIVE.state = Drive::SHRED_SELECTED; - menustate.bAbort = false; - menustate.bDelete = false; - menustate.bShred = false; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = true; } - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::Enter: //cout << "Enter" << endl; handleEnter(); - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; case TUI::UserInput::ESC: //cout << "ESC" << endl; handleESC(); - ui->updateTUI(&vecDrives, i32SelectedEntry, menustate); + ui->updateTUI(&vecDrives, i32SelectedEntry); break; default: break; @@ -385,27 +370,6 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) i32SelectedEntry = 0; break; } - - if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::TaskState::DELETE_ACTIVE) - { - //task for drive is running --> don´t show more task options - menustate.bAbort = true; //activate abort - menustate.bDelete = false; - menustate.bShred = false; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; - - } - else - { - //no task for drive is running --> show more task options - menustate.bAbort = false; //deactivate abort - menustate.bDelete = true; - menustate.bShred = true; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; - - } } void reHDD::handleEnter() @@ -414,11 +378,6 @@ void reHDD::handleEnter() { SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; //task for drive is running --> don´t show more task options - menustate.bAbort = true; //activate abort - menustate.bDelete = false; - menustate.bShred = false; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; //TODO start shredding } @@ -426,38 +385,22 @@ void reHDD::handleEnter() { SELECTED_DRIVE.state = Drive::TaskState::DELETE_ACTIVE; //task for drive is running --> don´t show more task options - menustate.bAbort = true; //activate abort - menustate.bDelete = false; - menustate.bShred = false; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; //TODO start deleting } } - void reHDD::handleESC() { if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::NONE; //task for drive is selected --> remove selection - menustate.bAbort = false; //activate abort - menustate.bDelete = true; - menustate.bShred = true; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; } if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) { SELECTED_DRIVE.state = Drive::TaskState::NONE; //task for drive is selected --> remove selection - menustate.bAbort = false; //activate abort - menustate.bDelete = true; - menustate.bShred = true; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; } } @@ -468,10 +411,5 @@ void reHDD::handleAbort() // TODO cancle shred or delete SELECTED_DRIVE.state = Drive::NONE; //task for drive is running --> remove selection - menustate.bAbort = false; //activate abort - menustate.bDelete = true; - menustate.bShred = true; - menustate.bConfirmDelete = false; - menustate.bConfirmShred = false; } } \ No newline at end of file From df4ed53693e3102913b70d2a5d56c539eaf83b3b Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 18 Aug 2020 22:12:10 +0200 Subject: [PATCH 17/41] added shred dummy script --- shred_dummy.sh | 10 ++++++++++ src/reHDD.cpp | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 shred_dummy.sh diff --git a/shred_dummy.sh b/shred_dummy.sh new file mode 100644 index 0000000..0a3eea2 --- /dev/null +++ b/shred_dummy.sh @@ -0,0 +1,10 @@ +#! /bin/bash + +echo "starting SHRED DUMMY" + +for i in {1..101..3} +do + echo "DUMMY shred $i%" +done + +echo "finished SHRED DUMMY" diff --git a/src/reHDD.cpp b/src/reHDD.cpp index b6d03c4..ee4454f 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -7,7 +7,6 @@ #include "../include/reHDD.h" - static int fdSearchDrives[2];//File descriptor for pipe that informs if new drives are found static int fdWhipe[2];//File descriptor for pipe that informs if a wipe thread signals From e4b3923f6dcf9148e785e2ec1d6075987c9fa1ee Mon Sep 17 00:00:00 2001 From: localhorst Date: Sat, 22 Aug 2020 21:48:34 +0200 Subject: [PATCH 18/41] parse percentage from shred cmd --- include/reHDD.h | 2 +- include/shred.h | 25 +++++++++++++++++ include/wipe.h | 25 ----------------- shred_dummy.sh | 3 ++- src/reHDD.cpp | 16 ++++++----- src/shred.cpp | 57 +++++++++++++++++++++++++++++++++++++++ src/wipe.cpp | 72 ------------------------------------------------- 7 files changed, 94 insertions(+), 106 deletions(-) create mode 100644 include/shred.h delete mode 100644 include/wipe.h create mode 100644 src/shred.cpp delete mode 100644 src/wipe.cpp diff --git a/include/reHDD.h b/include/reHDD.h index e527eee..67277fc 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -36,7 +36,7 @@ using namespace std; #include "drive.h" #include "smart.h" -#include "wipe.h" +#include "shred.h" #include "tui.h" diff --git a/include/shred.h b/include/shred.h new file mode 100644 index 0000000..6c25649 --- /dev/null +++ b/include/shred.h @@ -0,0 +1,25 @@ +/** + * @file shred.h + * @brief shred drive + * @author hendrik schutter + * @date 03.05.2020 + */ + +#ifndef SHRED_H_ +#define SHRED_H_ + +#include "reHDD.h" + +class Shred +{ +protected: + +public: + static void shredDrive(Drive* drive); + +private: + Shred(void); + +}; + +#endif // SHRED_H_ \ No newline at end of file diff --git a/include/wipe.h b/include/wipe.h deleted file mode 100644 index 21c8d5f..0000000 --- a/include/wipe.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @file wipe.h - * @brief wipe drive - * @author hendrik schutter - * @date 03.05.2020 - */ - -#ifndef WIPE_H_ -#define WIPE_H_ - -#include "reHDD.h" - -class Wipe -{ -protected: - -public: - static void wipeDrive(Drive* drive); - -private: - Wipe(void); - -}; - -#endif // WIPE_H_ \ No newline at end of file diff --git a/shred_dummy.sh b/shred_dummy.sh index 0a3eea2..bbdfed9 100644 --- a/shred_dummy.sh +++ b/shred_dummy.sh @@ -2,9 +2,10 @@ echo "starting SHRED DUMMY" -for i in {1..101..3} +for i in {0..100..10} do echo "DUMMY shred $i%" + sleep 1 done echo "finished SHRED DUMMY" diff --git a/src/reHDD.cpp b/src/reHDD.cpp index ee4454f..35783cf 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -9,7 +9,7 @@ static int fdSearchDrives[2];//File descriptor for pipe that informs if new drives are found -static int fdWhipe[2];//File descriptor for pipe that informs if a wipe thread signals +static int fdShred[2];//File descriptor for pipe that informs if a wipe thread signals static std::mutex mxScannDrives; @@ -48,11 +48,11 @@ void reHDD::app_logic(void) ui->initTUI(); pipe(fdSearchDrives); - pipe(fdWhipe); + pipe(fdShred); FD_ZERO(&selectSet); FD_SET(fdSearchDrives[0], &selectSet); - FD_SET(fdWhipe[0], &selectSet); + FD_SET(fdShred[0], &selectSet); thread thDevices(ThreadScannDevices); //start thread that scanns for drives thread thUserInput(ThreadUserInput); //start thread that reads user input @@ -73,11 +73,11 @@ void reHDD::app_logic(void) ui->updateTUI(&vecDrives, i32SelectedEntry); } - else if (FD_ISSET(fdWhipe[0], &selectSet)) + else if (FD_ISSET(fdShred[0], &selectSet)) { - cout << "Whipe signal" << endl; - //update percantage & state - //update ui + cout << "shred signal" << endl; + //TODO update percantage & state + //TODO update ui } } //endless loop thDevices.join(); @@ -377,7 +377,9 @@ void reHDD::handleEnter() { SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; //task for drive is running --> don´t show more task options + //TODO start shredding + Shred::shredDrive(&vecDrives.at(i32SelectedEntry)); } if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) diff --git a/src/shred.cpp b/src/shred.cpp new file mode 100644 index 0000000..646199b --- /dev/null +++ b/src/shred.cpp @@ -0,0 +1,57 @@ +/** + * @file shred.cpp + * @brief shred drive + * @author hendrik schutter + * @date 03.05.2020 + */ + +#include "../include/reHDD.h" + +/** + * \brief shred drive with shred + * \param pointer of Drive instance + * \return void + */ +void Shred::shredDrive(Drive* drive) + +{ + size_t len = 0; //lenght of found line + char* cLine = NULL; //found line + +#ifndef DRYRUN + string sCMD = ("shred -v "); + sCMD.append(drive->getPath()); +#endif + +#ifdef DRYRUN + cout << "dryrun for " << drive->getPath() << endl; + // string sCMD = ("ping ::1 -c 5"); + string sCMD = ("bash shred_dummy.sh"); +#endif + + const char* cpComand = sCMD.c_str(); + cout << "shred: " << cpComand << endl; + + FILE* shredCmdOutput = popen(cpComand, "r"); + + while ((getline(&cLine, &len, shredCmdOutput)) != -1) + { + string sLine = string(cLine); + + + // TODO parse percentage + + string search("%"); + size_t found = sLine.find(search); + if (found!=string::npos){ + uint8_t percent = 0U; + sLine.erase(0, sLine.find("%")-2); + sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); + int i = std::stoi(sLine); + cout << i << " percent" << endl; + } + + + } + fclose(shredCmdOutput); +} diff --git a/src/wipe.cpp b/src/wipe.cpp deleted file mode 100644 index d37e5b0..0000000 --- a/src/wipe.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @file wipe.cpp - * @brief wipe drive - * @author hendrik schutter - * @date 03.05.2020 - */ - -#include "../include/reHDD.h" - -//#define DRYRUN //if defined the drive will not be wiped, a ping cmd is attempted instead - -/** - * \brief wipe drive with shred - * \param pointer of Drive instance - * \return void - */ -void Wipe::wipeDrive(Drive* drive) -{ - size_t len = 0; //lenght of found line - char* cLine = NULL; //found line - -#ifndef DRYRUN - string sCMD = ("shred -v "); - sCMD.append(drive->getPath()); -#endif -#ifdef DRYRUN - cout << "dryrun for " << drive->getPath() << endl; - string sCMD = ("ping ::1 -c 5"); -#endif - const char* cpComand = sCMD.c_str(); - - cout << "wipe: " << cpComand << endl; - - auto t_start = chrono::high_resolution_clock::now(); - - FILE* outputfileSmart = popen(cpComand, "r"); - - while ((getline(&cLine, &len, outputfileSmart)) != -1) - { - string sLine = string(cLine); - cout << sLine; - } - fclose(outputfileSmart); - - auto t_end = chrono::high_resolution_clock::now(); - - uint64_t u64TimeMS = chrono::duration(t_end-t_start).count(); - - uint64_t u64hours = 0U; - uint64_t u64minutes = 0U; - uint64_t u64seconds = 0U; - - while(u64TimeMS >= 1000) - { - u64seconds++; - u64TimeMS = u64TimeMS - 1000; - } - - while(u64seconds >= 60) - { - u64minutes++; - u64seconds = u64seconds - 60; - } - - while(u64minutes >= 60) - { - u64hours++; - u64minutes = u64minutes - 60; - } - - cout << "Elapsed time: " << u64hours << " h - " << u64minutes << " min - " << u64seconds << " sec" << endl; -} From c6a8fd861d84c65ad1087e4c8dbe63f41426c9c8 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 23 Aug 2020 09:26:32 +0200 Subject: [PATCH 19/41] shred thread --- doc/diagrams/refurbishingHddTool.drawio | 2 +- doc/diagrams/refurbishingHddTool.pdf | Bin 56131 -> 49322 bytes doc/diagrams/states.drawio | 1 + doc/diagrams/states.png | Bin 0 -> 62516 bytes include/drive.h | 9 +-- include/reHDD.h | 4 +- include/shred.h | 2 +- include/tui.h | 2 +- shred_dummy.sh | 2 +- src/reHDD.cpp | 70 ++++++++++++++++-------- src/shred.cpp | 31 ++++------- src/{TUI => }/tui.cpp | 41 +++++++++++--- 12 files changed, 106 insertions(+), 58 deletions(-) create mode 100644 doc/diagrams/states.drawio create mode 100644 doc/diagrams/states.png rename src/{TUI => }/tui.cpp (90%) diff --git a/doc/diagrams/refurbishingHddTool.drawio b/doc/diagrams/refurbishingHddTool.drawio index 3667556..9a4d547 100644 --- a/doc/diagrams/refurbishingHddTool.drawio +++ b/doc/diagrams/refurbishingHddTool.drawio @@ -1 +1 @@ -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 \ No newline at end of file +5Vtbd5s4EP41Pmf3wT4gA3YefW26TXN2m3TT7kuPDLLRRkZUiNjOr18JhAGLxmRTbJr4xWgkdJmZb2Y0Ep3+ZL19x2Dof6QeIh1geNtOf9oBwDSHpviTlF1KsZ1hSlgx7KlGOeEGPyJFNBQ1xh6KSg05pYTjsEx0aRAgl5dokDG6KTdbUlIeNYQrpBFuXEh06h32uJ9Sh2CQ0y8RXvnZyKZzkdasYdZYrSTyoUc3BVJ/1ulPGKU8fVpvJ4hI5mV8uXu/uyNX9867P/6KvsPP4w+31393087mz3llvwSGAv6/u7bib1++z5aTb5fX79n19dXdzeqxq9Ya8V3GL+QJ9qkiZdynKxpAMsupY0bjwEOyV0OU8jZXlIaCaAriv4jzndIFGHMqSD5fE1UrVsF2X+T7PTsrflXdJYXptlTaqVJNNih2RTRmLnpi7UqsHLIV4k+066ftJGMKKqWY/A7RNRKTFA0YIpDjh7LeQaW+q327XETiQUnpGRJTs36AJFYj3dyOPt3qciREQEzKa+Njjm5CmDBjI1BelsbzuPqAGEfbJ/mgavsDpVybHHamoabvFyDnGA2xyjR1rrwZ7e7X1O7MTrdEvfuaegdoI90Pk0OLf8SFl5DymWvCZT5dL+LouMrvLbnk+5IGfA7XmEhBXCLygDh2oapQUjYFQ8aQ4FUgCq4QEGKyG87o/d6rJB2LIXGwEiUnL90mitO1ToG13QHOjmHvojHsOefA3k/GkFnXRZh2q0Bk6k6ijKKlZLSOnz37zeMYOkTHisAoUpKrAEYRcY2hwCmjAJgDHQWgAgVWYyiwXgEK6nqSdsVJ5lsObTN7dNxwDdolNFszXB3gELGCsYcfxONKPn5CUBgv43I6lf0FS5q1EWMWmlW8+dsaBvESujxmwoODidzmpTte+RgJqf7+o75ena20yrZyWBEx9Ctspd2UrQRvOVrPcHgUsKBd4Xo271qAvel97I16n3pis2rQpVytjzIc1wXwjDHKohSvvuCqehSQxg9ivTSI3iqC+3ZNBDe23wavIdoBdWN+cLZ45/PdPx+AOZ4N54/egl+5kyC87J7ZepZsZ25Kn7SeB5w7hxD7LxRZ8uqIMbgrNAgpDnhU6PlPSSggFwxLyLUGdlH8R9ubln2gLukMcuXZL+UFYNb3kJ8jGTQZQgnEThJHIUlXnfTlQQ574v8jZPdStMpMG1DuMw1fWJ/cYM8L1jqJ4BKOv8L9qDXoKdHuRW1VGGkH9GzdTA8aM9Pn2RltMS/EWaL0tVCT2wlZyMxES4IzUHcHDJxWBWeZ8z+ToAtizoVeLeizCaxdeTugZ7/j1ObiIIy5biDrJrwbs3CDcgxqV5k3syIGHTZl3KyzxKCnNG4o8EbyXFwUXenjsJsS55g0FRLXzSZZ7UoBAj2blOaMoOuikL804mgKVPtNmwKVUwUq40IHVWNpbNs+C6h+HYA4NQFig3YBxKkGiAPXUteDRRQmzDKWMCZ810649A9C7JPCpXIrfha0VCrWT9Rxq25MZRvV4jqNSlt6CDVjMNrnGY0sO7jIEoMRXCKyKyQNF3nKUOgQ99OXvsc0ae0nO9Ws2GsnJg5dyACcGxODs3oQs9Oq9JT9vBzjqfNTNihrzxAc3Cw80t4eGgf60kB+ytbzUxmGoxAGGYZT0HMYyazUEgc48hP8pjaAHR41iKkU324nuvcZ/Ew+lbd9Tonus1z2eVl2qEF0g1/CU2bTLABoigjiuavUnN8Gh2gZtRMUh2HgSV1eJYMdU2NwQDXmieXxMoe0e4aSCdiFZKQq1tjzUgihCD/CRdKV1Gxl2EW/9rhjT2VfAjWRyoo3yHzbLOeBzCwMKzC/6igSNMZ7Xbl3SNfcV8F8yy67A/NCv/bWFPMrvYEegmuMj3wYyke8Tj7fKErgkOFcuoM99QouEPlTsFWeHonaBeWcrkUDIivG0L1fJeZoQgllyVj9ZfITTZLBRlGYfmYiZQazwhJvpQEbq/lMfc7l9ykjyQgwd70A9LBLAxFAeIj1XDEimMsjL/En6fJQK8LrkKBujLsruJa6NrdNyUhz0DXBsBfKa8HC6XGoZi4vO2VKoQm7rp78WCkssxyTmbo5tIYV1nDYkFJYmlKoWyHlDHd6GsjFLiupTI8PWSyJkfBOydXvNJ5LThdJRA/fOQrx9DQwU5CABlIBl5iQA1J9U1Dl8srhTWNiHh4k4LM7GEWvZzk/BfyimH/slIbx+Sdj/dl/ \ No newline at end of file diff --git a/doc/diagrams/refurbishingHddTool.pdf b/doc/diagrams/refurbishingHddTool.pdf index 6989d9c12accdd2c3a57b398be016de4fd634744..db66b70d575627b10f8ed52c027aea336a9026aa 100644 GIT binary patch delta 46577 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 literal 56131 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 Zof{&hz{rJ;FvBpE(Dm$IUvF|#{|(csqgem| diff --git a/doc/diagrams/states.drawio b/doc/diagrams/states.drawio new file mode 100644 index 0000000..661e8c0 --- /dev/null +++ b/doc/diagrams/states.drawio @@ -0,0 +1 @@ +5Vtbd6M2EP41fkwOkrg+Ojbb3Z5sNo192u4jiRWbXYxcWV7b/fUVRgIL4Usag0jykqBBkmFmvtE3I9FDg/nmNxotZl/JBCc9aE02PTTsQeh6Dv+bCba5ANl+LpjSeJKLQCkYxf9iIbSEdBVP8FLpyAhJWLxQhU8kTfETU2QRpWStdnsmifqri2iKNcHoKUp06V/xhM1yqQ+9Uv4Zx9OZ/GXgBvmdeSQ7izdZzqIJWe+JUNhDA0oIy6/mmwFOMt1JveTjPh24WzwYxSk7ZwD78uOPwA1nv4++jj9B9/4n+7a+ssXTLtlWvjGecAWIJqFsRqYkjZKwlN5QskonOJvW4q2yzy0hCy4EXPgDM7YV1oxWjHDRjM0TcRdvYvZ3NvzaEa3ve3eGGzHzrrGVjZTR7d6grPl9/145bNeS4/L3y17qoN6EaElW9AkfUZb0v4hOMTvSDxbW5ajAZI758/BxFCcRi3+pzxEJ/5wW/UoT8gthxRdYVMz7K0pW4pd60E344948E/7C+6Z2/1kReeNquTNWn3cA/mJT3uRX0+z/aNx/GMup+JPls+X3dB9KEg7QzFfWs5jh0SLaKXbNQ4TqCeJhMWV4c9w+uj7FACQRJyOMwNu6hGsgRLM9pNpWQwaANQaoqEcFUAsqcmxFRYGuIgBrdOQ2pSMANJ20EXbKEGJ7Sgy5hs6JMLJr3WMacwVgevnYgs6MLZ7R2KK7crtmMxX5z7WO/0rr7Ib2KY22ex0WJE7Zcm/m+0xQotsGKroBqhCBSn8HwWP9+UX+BKWfFK/y/10HaVFxtKNjlP8dzSj3D7G0PFK5qgyLDkOccNR1LowCcGYcbWytsRta7O++3YXnrvVcn5l8ySj5iQckIVl0TEmaYf85TpKKaM8k2bQC9dCWbTFxdj8zVsypeD+JpymXzePJZBdSIiF44obLgvFlaIRqXL/GtlabtnW6xyN49qYCwDNOJBzDKxKwXZVJBL53Yl1qmkq4Zy5WyMRiBQPVhaB1fLFCHjzWv5nFCnga9sLRoNbTbqNHnKjeoUWng3GMYh6Io8fdfJkTCOXxyZ2bnjM8BlpR7xCDe0WVYd9hjiDmIMQ5NXaQKNG8zBNK08ku5Pl5yT2wivELGAgiw7A3RUTPxTYIXgnuV0VlV4fP3Th8KPjcFi91urdDmOiQku6tdecmzY0RAj0ovZjswdrKzueHMOPao/A2HIyzyw9F/KCNVEO7ppmf3z3mZ1eW4Q4wP9fwEuD5Sg0JXFvINsz8gi4zv2rZ4RTzs+VOUavMT8fe+2F+7gnm5yDXVVQuNxO6SwSRHho/BhE8F+rwtSXJVwXp4B0QQW3pM04EZdnx4kxwyBngOPy4VND2K6Y2TgVBbd28XvsNLURZ+Msd52J1dKnCI3X0OkDBxrSs19GHb17LDuialvXVwHhwr2b5sA7xreY10HReg5BS0eZ5DSh6GMprpAG6mdigSkkbtZGoQL0a1K91nDeXpsBTaYqN1AM/Iop0OEnxzUK697KjLiaAC00At/DJeuCe7A8sp3mg2/qBPlmwHZD5ovZgRAu4p4Rxy5BsVGA1EgfQgcxVTH/FVyrL87qOfajnav0iu+0/clx3jwPVHX1oNcGFus9fdqujPxh/+fOjnXCBdsXOjunsFupnXER96I3nXtCvqLrmULLbZu4F38CekvncCxk/VhBU95TM516d3lSyobpj0UruhfRy0TvJvdABI5a5l1U5Jtn95MvMF04lpoOg8q0BKAssXce0kbTMBS9Ly6r9A7/5EGDrtL7YPHnnadmBk2VlWga9SkLR/W1kCYgOZ2kaWzKepSEdAxfehvyQaVp1E7LJNI03yw+QczCVX3Gj8D8= \ No newline at end of file diff --git a/doc/diagrams/states.png b/doc/diagrams/states.png new file mode 100644 index 0000000000000000000000000000000000000000..41c861cb37134ed13bab2f2012d8c738bd56fd63 GIT binary patch literal 62516 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 Sy7%rHaCv+9x(i*yPy7p$__*c( literal 0 HcmV?d00001 diff --git a/include/drive.h b/include/drive.h index b7b1423..ba11ce0 100644 --- a/include/drive.h +++ b/include/drive.h @@ -17,11 +17,12 @@ public: enum TaskState {NONE, SHRED_SELECTED, SHRED_ACTIVE, - SHRED_FINISHED, DELETE_SELECTED, - DELETE_ACTIVE, - DELETE_FINISHED + DELETE_ACTIVE } state; + + bool bWasShredded = false; + bool bWasDeleteted = false; private: string sPath; @@ -33,7 +34,7 @@ private: uint32_t u32PowerOnHours = 0U; //in hours uint32_t u32PowerCycles = 0U; - uint8_t u8TaskPercentage = 0U; //in percent for Shred (1 to 100) and Delete (1 OR 100) + uint8_t u8TaskPercentage = 0U; //in percent for Shred (1 to 100) protected: diff --git a/include/reHDD.h b/include/reHDD.h index 67277fc..5bf31ea 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -62,10 +62,12 @@ private: static void addSMARTData(vector * pvecDrives); static void ThreadScannDevices(); static void ThreadUserInput(); - static void handleArrowKey(TUI::UserInput userInput); + static void ThreadShred(); + static void handleArrowKey(TUI::UserInput userInput); static void handleEnter(); static void handleESC(); static void handleAbort(); + static void checkShredComplete(vector * pvecDrives); }; diff --git a/include/shred.h b/include/shred.h index 6c25649..eba5cc8 100644 --- a/include/shred.h +++ b/include/shred.h @@ -15,7 +15,7 @@ class Shred protected: public: - static void shredDrive(Drive* drive); + static void shredDrive(Drive* drive, int* fdInformPipeWrite); private: Shred(void); diff --git a/include/tui.h b/include/tui.h index ab80348..c5f25dd 100644 --- a/include/tui.h +++ b/include/tui.h @@ -55,7 +55,7 @@ private: 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 *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, bool bSelected); + static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart,string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected); static WINDOW *createSystemStats(int iXSize, int iYSize, 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, string selectedTask, string optionA, string optionB); diff --git a/shred_dummy.sh b/shred_dummy.sh index bbdfed9..206624b 100644 --- a/shred_dummy.sh +++ b/shred_dummy.sh @@ -2,7 +2,7 @@ echo "starting SHRED DUMMY" -for i in {0..100..10} +for i in {0..100..20} do echo "DUMMY shred $i%" sleep 1 diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 35783cf..7539bb9 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -7,9 +7,9 @@ #include "../include/reHDD.h" -static int fdSearchDrives[2];//File descriptor for pipe that informs if new drives are found +static int fdNewDrivesInformPipe[2];//File descriptor for pipe that informs if new drives are found -static int fdShred[2];//File descriptor for pipe that informs if a wipe thread signals +static int fdShredInformPipe[2];//File descriptor for pipe that informs if a wipe thread signals static std::mutex mxScannDrives; @@ -47,38 +47,42 @@ void reHDD::app_logic(void) ui = new TUI(); ui->initTUI(); - pipe(fdSearchDrives); - pipe(fdShred); + pipe(fdNewDrivesInformPipe); + pipe(fdShredInformPipe); + - FD_ZERO(&selectSet); - FD_SET(fdSearchDrives[0], &selectSet); - FD_SET(fdShred[0], &selectSet); thread thDevices(ThreadScannDevices); //start thread that scanns for drives thread thUserInput(ThreadUserInput); //start thread that reads user input 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(fdSearchDrives[0], &selectSet)) + if( FD_ISSET(fdNewDrivesInformPipe[0], &selectSet)) { char dummy; - read (fdSearchDrives[0],&dummy,1); + read (fdNewDrivesInformPipe[0],&dummy,1); mxScannDrives.lock(); filterNewDrives(&vecDrives, &vecNewDrives); //filter and copy to app logic vector mxScannDrives.unlock(); - //TODO update UI + } - ui->updateTUI(&vecDrives, i32SelectedEntry); - } - else if (FD_ISSET(fdShred[0], &selectSet)) + if (FD_ISSET(fdShredInformPipe[0], &selectSet)) { - cout << "shred signal" << endl; - //TODO update percantage & state - //TODO update ui + char dummy; + read (fdShredInformPipe[0],&dummy,1); + + checkShredComplete(&vecDrives); } + + ui->updateTUI(&vecDrives, i32SelectedEntry); + } //endless loop thDevices.join(); thUserInput.join(); @@ -95,7 +99,7 @@ void reHDD::ThreadScannDevices() filterIgnoredDrives(&vecNewDrives); //filter out ignored drives addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives mxScannDrives.unlock(); - write (fdSearchDrives[1], "A",1); + write(fdNewDrivesInformPipe[1], "A",1); sleep(5); //sleep 5 sec } } @@ -157,6 +161,11 @@ void reHDD::ThreadUserInput() } } +void reHDD::ThreadShred() +{ + Shred::shredDrive(&vecDrives.at(i32SelectedEntry), &fdShredInformPipe[1]); +} + void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { @@ -171,9 +180,11 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN if(itOld->getSerial() == itNew->getSerial()) { bOldDriveIsOffline = false; - // cout << "already online drive found: " << itOld->getPath() << endl; - itNew->state = itOld->state; - itNew->setTaskPercentage(itOld->getTaskPercentage()); + // copy old drive instance date in new instance + itNew->state = itOld->state; //copy state + itNew->setTaskPercentage(itOld->getTaskPercentage()); //copy percentage + itNew->bWasDeleteted = itOld->bWasDeleteted; //copy finished task delete + itNew->bWasShredded = itOld->bWasShredded; //copy finished task shred } } @@ -377,9 +388,7 @@ void reHDD::handleEnter() { SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; //task for drive is running --> don´t show more task options - - //TODO start shredding - Shred::shredDrive(&vecDrives.at(i32SelectedEntry)); + thread(ThreadShred).detach(); } if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) @@ -413,4 +422,19 @@ void reHDD::handleAbort() SELECTED_DRIVE.state = Drive::NONE; //task for drive is running --> remove selection } +} + + +void reHDD::checkShredComplete(vector * pvecDrives) +{ + vector ::iterator it; + for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) + { + if(it->getTaskPercentage() == 100 ) + { + it->bWasShredded = true; //mark this drive as shredded + it->setTaskPercentage(0); //reset for an other shredding + it->state = Drive::NONE; //reset for an other task# + } + } } \ No newline at end of file diff --git a/src/shred.cpp b/src/shred.cpp index 646199b..65d0770 100644 --- a/src/shred.cpp +++ b/src/shred.cpp @@ -12,7 +12,7 @@ * \param pointer of Drive instance * \return void */ -void Shred::shredDrive(Drive* drive) +void Shred::shredDrive(Drive* drive, int* fdInformPipeWrite) { size_t len = 0; //lenght of found line @@ -24,34 +24,27 @@ void Shred::shredDrive(Drive* drive) #endif #ifdef DRYRUN - cout << "dryrun for " << drive->getPath() << endl; - // string sCMD = ("ping ::1 -c 5"); + //cout << "dryrun for " << drive->getPath() << endl; string sCMD = ("bash shred_dummy.sh"); #endif const char* cpComand = sCMD.c_str(); - cout << "shred: " << cpComand << endl; + //cout << "shred: " << cpComand << endl; FILE* shredCmdOutput = popen(cpComand, "r"); while ((getline(&cLine, &len, shredCmdOutput)) != -1) { string sLine = string(cLine); - - - // TODO parse percentage - - string search("%"); - size_t found = sLine.find(search); - if (found!=string::npos){ - uint8_t percent = 0U; - sLine.erase(0, sLine.find("%")-2); - sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); - int i = std::stoi(sLine); - cout << i << " percent" << endl; - } - - + string search("%"); + size_t found = sLine.find(search); + if (found!=string::npos) + { + sLine.erase(0, sLine.find("%")-3); + sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); + drive->setTaskPercentage(stoi(sLine)); + write(*fdInformPipeWrite, "A",1); + } } fclose(shredCmdOutput); } diff --git a/src/TUI/tui.cpp b/src/tui.cpp similarity index 90% rename from src/TUI/tui.cpp rename to src/tui.cpp index 4d3b7b8..2b1bd31 100644 --- a/src/TUI/tui.cpp +++ b/src/tui.cpp @@ -5,7 +5,7 @@ * @date 03.08.2020 */ -#include "../../include/reHDD.h" +#include "../include/reHDD.h" TUI::TUI(void) @@ -78,11 +78,39 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) if(i32SelectedEntry == (it - pvecDrives->begin())) { - bSelectedEntry = true; //mark this drive in entries list + bSelectedEntry = true; //mark this drive in entries list displaySelectedDrive(pvecDrives->at(i32SelectedEntry), stdscrX, stdscrY); } - WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, bSelectedEntry); + string sState = " "; + + switch (it->state) + { + case Drive::SHRED_ACTIVE: + sState = "Shredding: " + to_string(it->getTaskPercentage()) + "%"; + break; + + case Drive::DELETE_ACTIVE: + sState = "Deleting ..."; + break; + + case Drive::NONE: + case Drive::SHRED_SELECTED: + case Drive::DELETE_SELECTED: + if (it->bWasDeleteted) + { + sState = "DELETED"; //mark drive as deleted previously + } + if (it->bWasShredded) + { + sState = "SHREDDED"; //mark drive as shreded previously, overwrite if deleted + } + break; + default: + break; + } + + WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, sState, bSelectedEntry); wrefresh(tmp); } } @@ -212,7 +240,7 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive return newWindow; } -WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity, bool bSelected) +WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected) { WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, iXStart); @@ -236,6 +264,8 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, mvwaddstr(newWindow,2, 1, sModelName.c_str()); mvwaddstr(newWindow,3, 1, sCapacity.c_str()); + mvwaddstr(newWindow,2, iXSize-sState.length()-5, sState.c_str()); + keypad(newWindow, TRUE); return newWindow; @@ -345,9 +375,6 @@ void TUI::displaySelectedDrive(Drive drive, int stdscrX, int stdscrY) case Drive::SHRED_SELECTED : //shred task selected for this drive menustate.bConfirmShred = true; break; - case Drive::DELETE_FINISHED : //delete task finished for this drive - case Drive::SHRED_FINISHED : //shred task finished for this drive - break; default: break; } From ec300d7c75485d5940c722c479d1469c219d59af Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 23 Aug 2020 10:42:03 +0200 Subject: [PATCH 20/41] kill shred thread after abort or offline drive --- shred_dummy.sh | 2 +- src/reHDD.cpp | 3 ++- src/shred.cpp | 23 +++++++++++++++-------- src/tui.cpp | 5 +++-- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/shred_dummy.sh b/shred_dummy.sh index 206624b..bbdfed9 100644 --- a/shred_dummy.sh +++ b/shred_dummy.sh @@ -2,7 +2,7 @@ echo "starting SHRED DUMMY" -for i in {0..100..20} +for i in {0..100..10} do echo "DUMMY shred $i%" sleep 1 diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 7539bb9..dd44883 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -182,7 +182,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN bOldDriveIsOffline = false; // copy old drive instance date in new instance itNew->state = itOld->state; //copy state - itNew->setTaskPercentage(itOld->getTaskPercentage()); //copy percentage + itNew->setTaskPercentage(itOld->getTaskPercentage()); //copy percentage itNew->bWasDeleteted = itOld->bWasDeleteted; //copy finished task delete itNew->bWasShredded = itOld->bWasShredded; //copy finished task shred } @@ -192,6 +192,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN { //cout << "offline drive found: " << itOld->getPath() << endl; //TODO kill task thread if running + itOld->state = Drive::NONE; } } diff --git a/src/shred.cpp b/src/shred.cpp index 65d0770..32fc92b 100644 --- a/src/shred.cpp +++ b/src/shred.cpp @@ -35,15 +35,22 @@ void Shred::shredDrive(Drive* drive, int* fdInformPipeWrite) while ((getline(&cLine, &len, shredCmdOutput)) != -1) { - string sLine = string(cLine); - string search("%"); - size_t found = sLine.find(search); - if (found!=string::npos) + if(drive->state == Drive::SHRED_ACTIVE) { - sLine.erase(0, sLine.find("%")-3); - sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); - drive->setTaskPercentage(stoi(sLine)); - write(*fdInformPipeWrite, "A",1); + string sLine = string(cLine); + string search("%"); + size_t found = sLine.find(search); + if (found!=string::npos) + { + sLine.erase(0, sLine.find("%")-3); + sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); + drive->setTaskPercentage(stoi(sLine)); + write(*fdInformPipeWrite, "A",1); + } + } + else + { + return; //end shredding task } } fclose(shredCmdOutput); diff --git a/src/tui.cpp b/src/tui.cpp index 2b1bd31..b6521bd 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -67,12 +67,15 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) systemview=createSystemStats((int)(stdscrX/3), 10, (stdscrY-11)); wrefresh(systemview); + delwin(detailview); + vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) { string sModelFamily = it->getModelFamily(); string sModelName = it->getModelName(); string sCapacity = it->sCapacityToText(); + string sState = " "; bool bSelectedEntry = false; @@ -82,7 +85,6 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) displaySelectedDrive(pvecDrives->at(i32SelectedEntry), stdscrX, stdscrY); } - string sState = " "; switch (it->state) { @@ -298,7 +300,6 @@ WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart) WINDOW* TUI::createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate) { - WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, iXStart); From a2a18bbc34a8b7292edfe11e2be92dd01084efa6 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 23 Aug 2020 11:04:10 +0200 Subject: [PATCH 21/41] delete task --- include/delete.h | 25 +++++++++++++++++++++++++ include/drive.h | 2 +- include/reHDD.h | 1 + include/tui.h | 4 ++-- src/delete.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/reHDD.cpp | 10 +++++----- 6 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 include/delete.h create mode 100644 src/delete.cpp diff --git a/include/delete.h b/include/delete.h new file mode 100644 index 0000000..d1e1381 --- /dev/null +++ b/include/delete.h @@ -0,0 +1,25 @@ +/** + * @file delete.h + * @brief delete drive + * @author hendrik schutter + * @date 23.08.2020 + */ + +#ifndef DELETE_H_ +#define DELETE_H_ + +#include "reHDD.h" + +class Delete +{ +protected: + +public: + static void deleteDrive(Drive* drive); + +private: + Delete(void); + +}; + +#endif // DELETE_H_ diff --git a/include/drive.h b/include/drive.h index ba11ce0..a2b66c6 100644 --- a/include/drive.h +++ b/include/drive.h @@ -20,7 +20,7 @@ public: DELETE_SELECTED, DELETE_ACTIVE } state; - + bool bWasShredded = false; bool bWasDeleteted = false; diff --git a/include/reHDD.h b/include/reHDD.h index 5bf31ea..793a77e 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -37,6 +37,7 @@ using namespace std; #include "drive.h" #include "smart.h" #include "shred.h" +#include "delete.h" #include "tui.h" diff --git a/include/tui.h b/include/tui.h index c5f25dd..c90d005 100644 --- a/include/tui.h +++ b/include/tui.h @@ -45,7 +45,7 @@ private: static string sRamUsage; static string sLocalTime; - + WINDOW* overview; WINDOW* systemview; WINDOW* detailview; @@ -59,7 +59,7 @@ private: static WINDOW *createSystemStats(int iXSize, int iYSize, 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, string selectedTask, string optionA, string optionB); - + void displaySelectedDrive(Drive drive, int stdscrX, int stdscrY); }; diff --git a/src/delete.cpp b/src/delete.cpp new file mode 100644 index 0000000..a34db6e --- /dev/null +++ b/src/delete.cpp @@ -0,0 +1,41 @@ +/** + * @file delete.cpp + * @brief delete drive + * @author hendrik schutter + * @date 23.08.2020 + */ + +#include "../include/reHDD.h" + +/** + * \brief delete drive with wipefs + * \param pointer of Drive instance + * \return void + */ +void Delete::deleteDrive(Drive* drive) + +{ + size_t len = 0; //lenght of found line + char* cLine = NULL; //found line + +#ifndef DRYRUN + string sCMD = ("wipefs -af "); + sCMD.append(drive->getPath()); +#endif + +#ifdef DRYRUN + //cout << "dryrun for " << drive->getPath() << endl; + string sCMD = ("echo"); +#endif + + const char* cpComand = sCMD.c_str(); + //cout << "delete: " << cpComand << endl; + + FILE* deleteCmdOutput = popen(cpComand, "r"); + + while ((getline(&cLine, &len, deleteCmdOutput)) != -1) + { + //wipefs running + } + fclose(deleteCmdOutput); +} diff --git a/src/reHDD.cpp b/src/reHDD.cpp index dd44883..bfaf6c8 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -163,7 +163,7 @@ void reHDD::ThreadUserInput() void reHDD::ThreadShred() { - Shred::shredDrive(&vecDrives.at(i32SelectedEntry), &fdShredInformPipe[1]); + Shred::shredDrive(&SELECTED_DRIVE, &fdShredInformPipe[1]); } void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) @@ -191,7 +191,6 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN if(bOldDriveIsOffline == true) { //cout << "offline drive found: " << itOld->getPath() << endl; - //TODO kill task thread if running itOld->state = Drive::NONE; } } @@ -396,7 +395,9 @@ void reHDD::handleEnter() { SELECTED_DRIVE.state = Drive::TaskState::DELETE_ACTIVE; //task for drive is running --> don´t show more task options - //TODO start deleting + Delete::deleteDrive(&SELECTED_DRIVE); //blocking, no thread + SELECTED_DRIVE.state = Drive::TaskState::NONE; //delete finished + SELECTED_DRIVE.bWasDeleteted = true; } } @@ -419,7 +420,6 @@ void reHDD::handleAbort() { if(SELECTED_DRIVE.state == Drive::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::DELETE_ACTIVE ) { - // TODO cancle shred or delete SELECTED_DRIVE.state = Drive::NONE; //task for drive is running --> remove selection } @@ -435,7 +435,7 @@ void reHDD::checkShredComplete(vector * pvecDrives) { it->bWasShredded = true; //mark this drive as shredded it->setTaskPercentage(0); //reset for an other shredding - it->state = Drive::NONE; //reset for an other task# + it->state = Drive::NONE; //reset for an other task } } } \ No newline at end of file From f39fbfbd545741d1be2020ef6ec8f4b72cd04938 Mon Sep 17 00:00:00 2001 From: localhorst Date: Wed, 26 Aug 2020 00:12:39 +0200 Subject: [PATCH 22/41] added custom implementation for shred --- include/drive.h | 4 - include/reHDD.h | 12 +- include/shred.h | 25 --- include/shred/machdefs.h | 19 ++ include/shred/shred.h | 92 ++++++++++ include/shred/tfcore.h | 51 ++++++ include/shred/tfdef.h | 39 +++++ out.txt | 1 + shred_dummy.sh | 3 +- src/reHDD.cpp | 6 +- src/shred.cpp | 57 ------ src/shred/shred.cpp | 368 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 583 insertions(+), 94 deletions(-) delete mode 100644 include/shred.h create mode 100644 include/shred/machdefs.h create mode 100644 include/shred/shred.h create mode 100644 include/shred/tfcore.h create mode 100644 include/shred/tfdef.h create mode 100644 out.txt delete mode 100644 src/shred.cpp create mode 100644 src/shred/shred.cpp diff --git a/include/drive.h b/include/drive.h index a2b66c6..efe38fc 100644 --- a/include/drive.h +++ b/include/drive.h @@ -69,10 +69,6 @@ public: void setTaskPercentage(uint8_t u8TaskPercentage); uint8_t getTaskPercentage(void); - -private: - - }; #endif // DRIVE_H_ \ No newline at end of file diff --git a/include/reHDD.h b/include/reHDD.h index 793a77e..7a7399e 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,13 +8,18 @@ #ifndef REHDD_H_ #define REHDD_H_ -#define DRYRUN +//#define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 4000 //mark drive if at this limit or beyond +#define SHRED_ITERATIONS 1 + #define SELECTED_DRIVE vecDrives.at(i32SelectedEntry) +#define READ 0 +#define WRITE 1 + #include #include #include @@ -27,16 +32,17 @@ #include #include #include -#include +#include #include #include #include +#include using namespace std; #include "drive.h" #include "smart.h" -#include "shred.h" +#include "shred/shred.h" #include "delete.h" #include "tui.h" diff --git a/include/shred.h b/include/shred.h deleted file mode 100644 index eba5cc8..0000000 --- a/include/shred.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @file shred.h - * @brief shred drive - * @author hendrik schutter - * @date 03.05.2020 - */ - -#ifndef SHRED_H_ -#define SHRED_H_ - -#include "reHDD.h" - -class Shred -{ -protected: - -public: - static void shredDrive(Drive* drive, int* fdInformPipeWrite); - -private: - Shred(void); - -}; - -#endif // SHRED_H_ \ No newline at end of file diff --git a/include/shred/machdefs.h b/include/shred/machdefs.h new file mode 100644 index 0000000..a99d0eb --- /dev/null +++ b/include/shred/machdefs.h @@ -0,0 +1,19 @@ +#ifndef _MACHINE_DEFINITIONS_HEADER +#define _MACHINE_DEFINITIONS_HEADER + +#include +#include + +#undef MACHINE_16BIT +#undef MACHINE_32BIT +#undef MACHINE_64BIT + +#if UINTPTR_MAX == UINT32_MAX +#define MACHINE_32BIT +#elif UINTPTR_MAX == UINT64_MAX +#define MACHINE_64BIT +#elif UINTPTR_MAX == UINT16_MAX +#define MACHINE_16BIT +#endif + +#endif diff --git a/include/shred/shred.h b/include/shred/shred.h new file mode 100644 index 0000000..12ccbfd --- /dev/null +++ b/include/shred/shred.h @@ -0,0 +1,92 @@ +/** + * @file shred.h + * @brief shred drive + * @author hendrik schutter + * @date 03.05.2020 + */ + +#ifndef SHRED_H_ +#define SHRED_H_ + +#include "../reHDD.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "tfdef.h" +#include "tfcore.h" +//#include "tfe.h" + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#ifndef _TFNG_STREAM_CIPHER_DEFS +#define _TFNG_STREAM_CIPHER_DEFS +#endif + +#define PROCESS_BLOCKP(x,k1,k2,k3,k4,k5,k6) \ + do { \ + KE_MIX(Y, X, k1 + k2, k3, TFS_KS01); \ + KE_MIX(T, Z, k4 + x, k5 + k6, TFS_KS02); \ + \ + BE_MIX(X, T, TFS_BS01); BE_MIX(Z, Y, TFS_BS02); \ + BE_MIX(X, Y, TFS_BS03); BE_MIX(Z, T, TFS_BS04); \ + BE_MIX(X, T, TFS_BS05); BE_MIX(Z, Y, TFS_BS06); \ + } while (0) + +#define PROCESS_BLOCKN(x,k1,k2,k3,k4,k5,k6) \ + do { \ + KE_MIX(Y, X, k1 + k2, k3, TFS_KS03); \ + KE_MIX(T, Z, k4 + x, k5 + k6, TFS_KS04); \ + \ + BE_MIX(X, T, TFS_BS07); BE_MIX(Z, Y, TFS_BS08); \ + BE_MIX(X, Y, TFS_BS09); BE_MIX(Z, T, TFS_BS10); \ + BE_MIX(X, T, TFS_BS11); BE_MIX(Z, Y, TFS_BS12); \ + } while (0) + + +#define NOSIZE ((size_t)-1) + +#define XRET(x) if (!xret && xret < x) xret = x + +class Shred +{ +protected: + +public: + static void shredDrive(Drive* drive, int* ipSignalFd); + +private: + Shred(void); + static inline uint8_t calcProgress(); + static inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); + static inline void tfnge_init(struct tfnge_stream *tfe, const void *key); + static inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); + static inline void tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe); + + +}; + +#endif // SHRED_H_ diff --git a/include/shred/tfcore.h b/include/shred/tfcore.h new file mode 100644 index 0000000..2d9d53e --- /dev/null +++ b/include/shred/tfcore.h @@ -0,0 +1,51 @@ +#ifndef _THREEFISH_NOISE_GENERATOR_CIPHER_CORE_HEADER +#define _THREEFISH_NOISE_GENERATOR_CIPHER_CORE_HEADER + +#ifndef _THREEFISH_NOISE_GENERATOR_CIPHER_DEFINITIONS_HEADER +#error Threefish definitions header is required! Include tfdef.h first. +#endif + +#define ROL(x, s, max) ((x << s) | (x >> (-s & (max-1)))) +#define ROR(x, s, max) ((x >> s) | (x << (-s & (max-1)))) + +#define KE_MIX(x, y, k1, k2, sl) \ + do { \ + x += k1; \ + y += x; \ + y += k2; \ + x = ROL(x, sl, TFNG_UNIT_BITS); \ + x ^= y; \ + } while (0) + +#define BE_MIX(x, y, sl) \ + do { \ + x += y; \ + y = ROL(y, sl, TFNG_UNIT_BITS); \ + y ^= x; \ + } while (0) + +#define KD_MIX(x, y, k1, k2, sr) \ + do { \ + x ^= y; \ + x = ROR(x, sr, TFNG_UNIT_BITS); \ + y -= x; \ + y -= k2; \ + x -= k1; \ + } while (0) + +#define BD_MIX(x, y, sr) \ + do { \ + y ^= x; \ + y = ROR(y, sr, TFNG_UNIT_BITS); \ + x -= y; \ + } while (0) + +enum tfng_rotations +{ + TFS_KS01 = 7, TFS_KS02 = 25, TFS_KS03 = 19, TFS_KS04 = 7, + TFS_BS01 = 5, TFS_BS02 = 27, TFS_BS03 = 26, TFS_BS04 = 6, + TFS_BS05 = 14, TFS_BS06 = 11, TFS_BS07 = 24, TFS_BS08 = 18, + TFS_BS09 = 9, TFS_BS10 = 24, TFS_BS11 = 6, TFS_BS12 = 7, +}; + +#endif diff --git a/include/shred/tfdef.h b/include/shred/tfdef.h new file mode 100644 index 0000000..ea0bd0b --- /dev/null +++ b/include/shred/tfdef.h @@ -0,0 +1,39 @@ +#ifndef _THREEFISH_NOISE_GENERATOR_CIPHER_DEFINITIONS_HEADER +#define _THREEFISH_NOISE_GENERATOR_CIPHER_DEFINITIONS_HEADER + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif + +#include +#include +#include "machdefs.h" + +#if defined(MACHINE_64BIT) +#define TFNG_UNIT_TYPE uint64_t +#define TFNG_NR_BLOCK_BITS 256 +#define TFNG_NR_KEY_BITS 512 +#else +#define TFNG_UNIT_TYPE uint32_t +#define TFNG_NR_BLOCK_BITS 128 +#define TFNG_NR_KEY_BITS 256 +#endif + +#define TFNG_NR_BLOCK_UNITS 4 +#define TFNG_NR_KEY_UNITS 8 + +#define TFNG_BYTE_TYPE uint8_t +#define TFNG_SIZE_UNIT (sizeof(TFNG_UNIT_TYPE)) +#define TFNG_BLOCK_SIZE (TFNG_SIZE_UNIT * TFNG_NR_BLOCK_UNITS) +#define TFNG_KEY_SIZE (TFNG_SIZE_UNIT * TFNG_NR_KEY_UNITS) + +#define TFNG_TO_BITS(x) ((x) * 8) +#define TFNG_FROM_BITS(x) ((x) / 8) +#define TFNG_MAX_BITS TFNG_NR_BLOCK_BITS +#define TFNG_UNIT_BITS (TFNG_SIZE_UNIT * 8) + +#endif diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..60fffd1 --- /dev/null +++ b/out.txt @@ -0,0 +1 @@ +date diff --git a/shred_dummy.sh b/shred_dummy.sh index bbdfed9..afc4268 100644 --- a/shred_dummy.sh +++ b/shred_dummy.sh @@ -4,7 +4,8 @@ echo "starting SHRED DUMMY" for i in {0..100..10} do - echo "DUMMY shred $i%" + #echo "DUMMY shred $i%" + echo $date > out.txt sleep 1 done diff --git a/src/reHDD.cpp b/src/reHDD.cpp index bfaf6c8..084ee5f 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -50,8 +50,6 @@ void reHDD::app_logic(void) pipe(fdNewDrivesInformPipe); pipe(fdShredInformPipe); - - thread thDevices(ThreadScannDevices); //start thread that scanns for drives thread thUserInput(ThreadUserInput); //start thread that reads user input @@ -425,7 +423,6 @@ void reHDD::handleAbort() } } - void reHDD::checkShredComplete(vector * pvecDrives) { vector ::iterator it; @@ -438,4 +435,5 @@ void reHDD::checkShredComplete(vector * pvecDrives) it->state = Drive::NONE; //reset for an other task } } -} \ No newline at end of file +} + diff --git a/src/shred.cpp b/src/shred.cpp deleted file mode 100644 index 32fc92b..0000000 --- a/src/shred.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file shred.cpp - * @brief shred drive - * @author hendrik schutter - * @date 03.05.2020 - */ - -#include "../include/reHDD.h" - -/** - * \brief shred drive with shred - * \param pointer of Drive instance - * \return void - */ -void Shred::shredDrive(Drive* drive, int* fdInformPipeWrite) - -{ - size_t len = 0; //lenght of found line - char* cLine = NULL; //found line - -#ifndef DRYRUN - string sCMD = ("shred -v "); - sCMD.append(drive->getPath()); -#endif - -#ifdef DRYRUN - //cout << "dryrun for " << drive->getPath() << endl; - string sCMD = ("bash shred_dummy.sh"); -#endif - - const char* cpComand = sCMD.c_str(); - //cout << "shred: " << cpComand << endl; - - FILE* shredCmdOutput = popen(cpComand, "r"); - - while ((getline(&cLine, &len, shredCmdOutput)) != -1) - { - if(drive->state == Drive::SHRED_ACTIVE) - { - string sLine = string(cLine); - string search("%"); - size_t found = sLine.find(search); - if (found!=string::npos) - { - sLine.erase(0, sLine.find("%")-3); - sLine.erase(std::remove(sLine.begin(), sLine.end(), '\n'), sLine.end()); - drive->setTaskPercentage(stoi(sLine)); - write(*fdInformPipeWrite, "A",1); - } - } - else - { - return; //end shredding task - } - } - fclose(shredCmdOutput); -} diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp new file mode 100644 index 0000000..2f90949 --- /dev/null +++ b/src/shred/shred.cpp @@ -0,0 +1,368 @@ +/** + * @file shred.cpp + * @brief shred drive + * @author hendrik schutter + * @date 03.05.2020 + */ + +#include "../../include/reHDD.h" + +static char *randsrc = (char*) "/dev/urandom"; +static int force = 0, rmf = 0, zrf = 0, noround = 0, verbose = 0, syncio = 0, alwaysrand = 0, reqrand = 0; + +static char sfbuf[PATH_MAX*2]; + +struct tfnge_stream +{ + TFNG_UNIT_TYPE key[TFNG_NR_KEY_UNITS]; + TFNG_UNIT_TYPE iv[TFNG_NR_BLOCK_UNITS]; + TFNG_BYTE_TYPE carry_block[TFNG_BLOCK_SIZE]; + size_t carry_bytes; +}; + +static struct tfnge_stream tfnge; + +static unsigned long blockcount = 0UL; +static long blockcount_max; +static uint8_t u8Percent; + +/** + * \brief shred drive with shred + * \param pointer of Drive instance + * \return void + */ +void Shred::shredDrive(Drive* drive, int* ipSignalFd) +{ + +#ifdef DRYRUN +//TODO +#endif + +#ifndef DRYRUN + + struct stat st; + char *buf, *s, *d, rc = 0; + int f, rsf; + int xret = 0, pat = 0, last = 0, special = 0, iIteration = 0; + size_t blksz = 0, x, y; + size_t l, ll = NOSIZE; + + const char *cpDrivePath = drive->getPath().c_str(); + + blockcount_max = SHRED_ITERATIONS*(drive->getCapacity()/4096); + + u8Percent = 0U; + + rsf = open(randsrc, O_RDONLY | O_LARGEFILE); + if (rsf == -1) + { + perror(randsrc); + exit(1); + } + + special = pat = 0; + iIteration = SHRED_ITERATIONS; + if (verbose) fprintf(stderr, "destroying %s ...\n", cpDrivePath); + + if (stat(cpDrivePath, &st) == -1) + { + perror(cpDrivePath); + XRET(1); + goto _return; + } + if (!blksz) blksz = (size_t)st.st_blksize; + /* + if (howmany != -1) + { + l = ll = howmany; + noround = 1; + } + */ + else l = ll = st.st_size; + if (l == 0 && !S_ISREG(st.st_mode)) special = 1; + memset(&st, 0, sizeof(struct stat)); + + if (force) if (chmod(cpDrivePath, S_IRUSR|S_IWUSR) == -1) + { + perror(cpDrivePath); + XRET(1); + } + f = open(cpDrivePath, O_WRONLY | O_LARGEFILE | O_NOCTTY | syncio); + if (f == -1) + { + XRET(1); + perror(cpDrivePath); + goto _return; + } + + buf = (char*) malloc(blksz); + if (!buf) + { + perror("malloc"); + XRET(2); + fprintf(stderr, "Continuing with fixed buffer (%zu bytes long)\n", sizeof(sfbuf)); + buf = sfbuf; + blksz = sizeof(sfbuf); + } + memset(buf, 0, blksz); + + if (read(rsf, buf, blksz) <= 0) fprintf(stderr, "%s: read 0 bytes (wanted %zu)\n", randsrc, blksz); + tfnge_init(&tfnge, buf); + + //iteration loop + while (iIteration) + { + lseek(f, 0L, SEEK_SET); + + if (iIteration <= 1 && zrf) + { + pat = 1; + rc = 0; + } + else if (iIteration == SHRED_ITERATIONS && reqrand) + { + pat = 0; + } + else if (!alwaysrand) + { + if (read(rsf, &rc, 1) <= 0) fprintf(stderr, "%s: read 0 bytes (wanted 1)\n", randsrc); + pat = rc%2; + if (read(rsf, &rc, 1) <= 0) fprintf(stderr, "%s: read 0 bytes (wanted 1)\n", randsrc); + } + else pat = 0; + + if (verbose) + { + if (pat) fprintf(stderr, "iteration (pat) %d (%02hhx%02hhx%02hhx) ...\n", SHRED_ITERATIONS-iIteration+1, rc, rc, rc); + else fprintf(stderr, "iteration (!pat) %d (random) ...\n", SHRED_ITERATIONS-iIteration+1); + } + // write block loop + while (1) + { + + if(drive->state != Drive::SHRED_ACTIVE){ + goto _return; + } + + uint8_t u8TmpPercent = calcProgress(); + + if(u8Percent != u8TmpPercent){ + drive->setTaskPercentage(u8TmpPercent); + u8Percent = u8TmpPercent; + write(*ipSignalFd, "A",1); + } + + if (!pat) + { + tfnge_emit(buf, blksz, &tfnge); + } + else + { + memset(buf, rc, blksz); + } + + if (l <= blksz && !special) + { + last = 1; + } + errno = 0; + l -= write(f, buf, (noround && last) ? l : blksz); + if (errno) + { + perror(cpDrivePath); + errno = 0; + break; + } + + if (last) + { + last = 0; + break; + } + } + // write block loop end + l = ll; + fdatasync(f); + iIteration--; + } //iteration loop end + + if (rmf) + { + close(f); + f = open(cpDrivePath, O_WRONLY | O_TRUNC | O_LARGEFILE | O_NOCTTY | syncio); + if (verbose) fprintf(stderr, "removing %s ...\n", cpDrivePath); + x = strnlen(cpDrivePath, sizeof(sfbuf)/2); + s = sfbuf+(sizeof(sfbuf)/2); + memcpy(sfbuf, cpDrivePath, x); + *(sfbuf+x) = 0; + d = strrchr(sfbuf, '/'); + if (d) + { + d++; + y = d-sfbuf; + memset(d, '0', x-(d-sfbuf)); + } + else + { + y = 0; + memset(sfbuf, '0', x); + } + memcpy(s, sfbuf, x); + *(s+x) = 0; + + /* Somehow I need to rename original to destination */ + if (access(s, R_OK) != -1) + { + fprintf(stderr, "%s already exists!\n", s); + unlink(cpDrivePath); + goto _return; + } + if (verbose) fprintf(stderr, "%s -> %s\n", cpDrivePath, s); + if (rename(cpDrivePath, s) == -1) + { + perror(s); + goto _return; + } + + while (x > y+1) + { + *(sfbuf+x) = 0; + x--; + *(s+x) = 0; + if (access(s, R_OK) != -1) + { + fprintf(stderr, "%s already exists!\n", s); + unlink(sfbuf); + goto _return; + } + if (verbose) fprintf(stderr, "%s -> %s\n", sfbuf, s); + if (rename(sfbuf, s) == -1) + { + perror(s); + goto _return; + } + } + + if (verbose) fprintf(stderr, "remove %s\n", s); + unlink(s); + if (verbose) fprintf(stderr, "done away with %s.\n", cpDrivePath); + } + + tfnge_emit(NULL, 0, &tfnge); + if (buf && buf != sfbuf) free(buf); + if (f != -1) close(f); + +_return: + optind++; + close(rsf); +#endif +} + + +uint8_t Shred::calcProgress(){ +blockcount++; + return ((((double)blockcount/(double)blockcount_max))*100); +} + + +void Shred::tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv) +{ + memset(tfe, 0, sizeof(struct tfnge_stream)); + memcpy(tfe->key, key, TFNG_KEY_SIZE); + if (iv) memcpy(tfe->iv, iv, TFNG_BLOCK_SIZE); + tfe->carry_bytes = 0; +} + +void Shred::tfnge_init(struct tfnge_stream *tfe, const void *key) +{ + tfnge_init_iv(tfe, key, NULL); +} + +void Shred::tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K) +{ + TFNG_UNIT_TYPE X, Y, Z, T; + TFNG_UNIT_TYPE K0, K1, K2, K3; + TFNG_UNIT_TYPE K4, T0, T1, T2; + + X = I[0]; + Y = I[1]; + Z = I[2]; + T = I[3]; + + K0 = K[0]; + K1 = K[1]; + K2 = K[2]; + K3 = K[3]; + K4 = K[4]; + T0 = K[5]; + T1 = K[6]; + T2 = K[7]; + + PROCESS_BLOCKP( 1,K1,T0,K0,K3,K2,T1); + PROCESS_BLOCKN( 2,K2,T1,K1,K4,K3,T2); + PROCESS_BLOCKP( 3,K3,T2,K2,K0,K4,T0); + PROCESS_BLOCKN( 4,K4,T0,K3,K1,K0,T1); + + PROCESS_BLOCKP( 5,K0,T1,K4,K2,K1,T2); + PROCESS_BLOCKN( 6,K1,T2,K0,K3,K2,T0); + PROCESS_BLOCKP( 7,K2,T0,K1,K4,K3,T1); + PROCESS_BLOCKN( 8,K3,T1,K2,K0,K4,T2); + + PROCESS_BLOCKP( 9,K4,T2,K3,K1,K0,T0); + PROCESS_BLOCKN(10,K0,T0,K4,K2,K1,T1); + PROCESS_BLOCKP(11,K1,T1,K0,K3,K2,T2); + PROCESS_BLOCKN(12,K2,T2,K1,K4,K3,T0); + + O[0] = X + K3; + O[1] = Y + K4 + T0; + O[2] = Z + K0 + T1; + O[3] = T + K1 + 18; +} + +void Shred::tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe) +{ + TFNG_BYTE_TYPE *udst = (uint8_t*) dst; + size_t sz = szdst; + + if (!dst && szdst == 0) + { + memset(tfe, 0, sizeof(struct tfnge_stream)); + return; + } + + if (tfe->carry_bytes > 0) + { + if (tfe->carry_bytes > szdst) + { + memcpy(udst, tfe->carry_block, szdst); + memmove(tfe->carry_block, tfe->carry_block+szdst, tfe->carry_bytes-szdst); + tfe->carry_bytes -= szdst; + return; + } + + memcpy(udst, tfe->carry_block, tfe->carry_bytes); + udst += tfe->carry_bytes; + sz -= tfe->carry_bytes; + tfe->carry_bytes = 0; + } + + if (sz >= TFNG_BLOCK_SIZE) + { + do + { + tfng_encrypt_rawblk(tfe->iv, tfe->iv, tfe->key); + memcpy(udst, tfe->iv, TFNG_BLOCK_SIZE); + udst += TFNG_BLOCK_SIZE; + } + while ((sz -= TFNG_BLOCK_SIZE) >= TFNG_BLOCK_SIZE); + } + + if (sz) + { + tfng_encrypt_rawblk(tfe->iv, tfe->iv, tfe->key); + memcpy(udst, tfe->iv, sz); + udst = (TFNG_BYTE_TYPE *)tfe->iv; + tfe->carry_bytes = TFNG_BLOCK_SIZE-sz; + memcpy(tfe->carry_block, udst+sz, tfe->carry_bytes); + } +} \ No newline at end of file From 424218bb23f5646bbec55d74f387a5a7b89f2a54 Mon Sep 17 00:00:00 2001 From: localhorst Date: Wed, 26 Aug 2020 15:07:53 +0200 Subject: [PATCH 23/41] dryrun and set states for new drives --- include/reHDD.h | 2 +- include/shred/shred.h | 10 +++++----- src/reHDD.cpp | 1 + src/shred/shred.cpp | 46 ++++++++++++++++++++++++++++--------------- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 7a7399e..c357efc 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,7 +8,7 @@ #ifndef REHDD_H_ #define REHDD_H_ -//#define DRYRUN +#define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 4000 //mark drive if at this limit or beyond diff --git a/include/shred/shred.h b/include/shred/shred.h index 12ccbfd..b1efaa1 100644 --- a/include/shred/shred.h +++ b/include/shred/shred.h @@ -80,11 +80,11 @@ public: private: Shred(void); - static inline uint8_t calcProgress(); - static inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); - static inline void tfnge_init(struct tfnge_stream *tfe, const void *key); - static inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); - static inline void tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe); + static inline uint8_t calcProgress(); + static inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); + static inline void tfnge_init(struct tfnge_stream *tfe, const void *key); + static inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); + static inline void tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe); }; diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 084ee5f..9406de5 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -223,6 +223,7 @@ void reHDD::searchDrives(vector * pvecDrives) if (string(cLine).find("/dev/sd") != string::npos) { Drive* tmpDrive = new Drive(string(cLine).substr (2,8)); + tmpDrive->state = Drive::NONE; pvecDrives->push_back(*tmpDrive); } } diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index 2f90949..605f2a3 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -6,7 +6,7 @@ */ #include "../../include/reHDD.h" - +#ifndef DRYRUN static char *randsrc = (char*) "/dev/urandom"; static int force = 0, rmf = 0, zrf = 0, noround = 0, verbose = 0, syncio = 0, alwaysrand = 0, reqrand = 0; @@ -25,7 +25,7 @@ static struct tfnge_stream tfnge; static unsigned long blockcount = 0UL; static long blockcount_max; static uint8_t u8Percent; - +#endif /** * \brief shred drive with shred * \param pointer of Drive instance @@ -33,9 +33,18 @@ static uint8_t u8Percent; */ void Shred::shredDrive(Drive* drive, int* ipSignalFd) { - #ifdef DRYRUN -//TODO + for(int i = 0; i<=10; i++) + { + if(drive->state != Drive::SHRED_ACTIVE) + { + return; + } + drive->setTaskPercentage(i*10); + write(*ipSignalFd, "A",1); + + sleep(2); + } #endif #ifndef DRYRUN @@ -139,18 +148,21 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) // write block loop while (1) { + usleep(10); - if(drive->state != Drive::SHRED_ACTIVE){ - goto _return; - } + if(drive->state != Drive::SHRED_ACTIVE) + { + goto _return; + } uint8_t u8TmpPercent = calcProgress(); - if(u8Percent != u8TmpPercent){ - drive->setTaskPercentage(u8TmpPercent); - u8Percent = u8TmpPercent; - write(*ipSignalFd, "A",1); - } + if(u8Percent != u8TmpPercent) + { + drive->setTaskPercentage(u8TmpPercent); + u8Percent = u8TmpPercent; + write(*ipSignalFd, "A",1); + } if (!pat) { @@ -257,10 +269,11 @@ _return: close(rsf); #endif } +#ifndef DRYRUN - -uint8_t Shred::calcProgress(){ -blockcount++; +uint8_t Shred::calcProgress() +{ + blockcount++; return ((((double)blockcount/(double)blockcount_max))*100); } @@ -365,4 +378,5 @@ void Shred::tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe) tfe->carry_bytes = TFNG_BLOCK_SIZE-sz; memcpy(tfe->carry_block, udst+sz, tfe->carry_bytes); } -} \ No newline at end of file +} +#endif \ No newline at end of file From 320e306d06f41bde8c31957db4bc0a3fb64f70e5 Mon Sep 17 00:00:00 2001 From: localhorst Date: Wed, 26 Aug 2020 18:38:39 +0200 Subject: [PATCH 24/41] fixed states if drive is no longer present --- include/reHDD.h | 6 +- include/tui.h | 3 +- makefile | 2 +- src/reHDD.cpp | 129 +++++++++++++++++++++++++--------------- src/tui.cpp | 58 +++++++++++++++++- vcCodium.code-workspace | 3 +- 6 files changed, 145 insertions(+), 56 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index c357efc..79e6f5a 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -15,7 +15,9 @@ #define SHRED_ITERATIONS 1 -#define SELECTED_DRIVE vecDrives.at(i32SelectedEntry) + + + #define READ 0 #define WRITE 1 @@ -75,7 +77,7 @@ private: static void handleESC(); static void handleAbort(); static void checkShredComplete(vector * pvecDrives); - + static Drive* getSelectedDrive(); }; diff --git a/include/tui.h b/include/tui.h index c90d005..3d4c3a9 100644 --- a/include/tui.h +++ b/include/tui.h @@ -36,7 +36,7 @@ public: static void initTUI(); - void updateTUI(vector * pvecDrives, int32_t i32SelectedEntry); + void updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry); static enum UserInput readUserInput(); @@ -55,6 +55,7 @@ private: 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,string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected); static WINDOW *createSystemStats(int iXSize, int iYSize, int iYStart); static WINDOW *createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate); diff --git a/makefile b/makefile index 66f9bac..4e3c0b5 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ SRC_PATH = src # Space-separated pkg-config libraries used by this project LIBS = # General compiler flags -COMPILE_FLAGS = -std=c++11 -Wall -Wextra -g +COMPILE_FLAGS = -std=c++17 -Wall -Wextra -g # Additional release-specific flags RCOMPILE_FLAGS = -D NDEBUG # Additional debug-specific flags diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 9406de5..47f12c5 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -19,7 +19,7 @@ static vector vecDrives; //stores all drive data from scann thread TUI *ui; -static int32_t i32SelectedEntry; +static uint8_t u8SelectedEntry; static fd_set selectSet; @@ -33,7 +33,7 @@ static fd_set selectSet; reHDD::reHDD(void) { cout << "created app" << endl; - i32SelectedEntry = 0; + u8SelectedEntry = 0U; } /** @@ -78,14 +78,24 @@ void reHDD::app_logic(void) checkShredComplete(&vecDrives); } - - ui->updateTUI(&vecDrives, i32SelectedEntry); - + ui->updateTUI(&vecDrives, u8SelectedEntry); } //endless loop thDevices.join(); thUserInput.join(); } +Drive* reHDD::getSelectedDrive() +{ + if(u8SelectedEntry < vecDrives.size() ) + { + return &(vecDrives.at(u8SelectedEntry)); + } + else + { + return {}; + } +} + void reHDD::ThreadScannDevices() { while(true) @@ -112,12 +122,12 @@ void reHDD::ThreadUserInput() case TUI::UserInput::DownKey: //cout << "Down" << endl; handleArrowKey(TUI::UserInput::DownKey); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::UpKey: //cout << "Up" << endl; handleArrowKey(TUI::UserInput::UpKey); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::Undefined: //cout << "Undefined" << endl; @@ -125,33 +135,41 @@ void reHDD::ThreadUserInput() case TUI::UserInput::Abort: //cout << "Abort" << endl; handleAbort(); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::Delete: //cout << "Delete" << endl; - if(SELECTED_DRIVE.state == Drive::NONE) + + if (getSelectedDrive() != nullptr) { - SELECTED_DRIVE.state = Drive::DELETE_SELECTED; + if(getSelectedDrive()->state == Drive::NONE) + { + getSelectedDrive()->state = Drive::DELETE_SELECTED; + } } - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::Shred: //cout << "Shred" << endl; - if(SELECTED_DRIVE.state == Drive::NONE) + + if (getSelectedDrive() != nullptr) { - SELECTED_DRIVE.state = Drive::SHRED_SELECTED; + if(getSelectedDrive()->state == Drive::NONE) + { + getSelectedDrive()->state = Drive::SHRED_SELECTED; + } } - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::Enter: //cout << "Enter" << endl; handleEnter(); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; case TUI::UserInput::ESC: //cout << "ESC" << endl; handleESC(); - ui->updateTUI(&vecDrives, i32SelectedEntry); + ui->updateTUI(&vecDrives, u8SelectedEntry); break; default: break; @@ -161,7 +179,10 @@ void reHDD::ThreadUserInput() void reHDD::ThreadShred() { - Shred::shredDrive(&SELECTED_DRIVE, &fdShredInformPipe[1]); + if (getSelectedDrive() != nullptr) + { + Shred::shredDrive(getSelectedDrive(), &fdShredInformPipe[1]); + } } void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) @@ -358,69 +379,81 @@ void reHDD::addSMARTData(vector * pvecDrives) void reHDD::handleArrowKey(TUI::UserInput userInput) { - int32_t i32EntrySize = (int32_t) vecDrives.size(); + int8_t u8EntrySize = (int8_t) vecDrives.size(); switch (userInput) { case TUI::UserInput::DownKey: - i32SelectedEntry++; - if(i32SelectedEntry >= i32EntrySize) + u8SelectedEntry++; + if(u8SelectedEntry >= u8EntrySize) { - i32SelectedEntry = 0; + u8SelectedEntry = 0; } break; case TUI::UserInput::UpKey: - i32SelectedEntry--; - if(i32SelectedEntry < 0) + if(u8SelectedEntry == 0) { - i32SelectedEntry = (i32EntrySize-1); + u8SelectedEntry = (u8EntrySize-1); + } + else + { + u8SelectedEntry--; } break; default: - i32SelectedEntry = 0; + u8SelectedEntry = 0; break; } } void reHDD::handleEnter() { - if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) + if (getSelectedDrive() != nullptr) { - SELECTED_DRIVE.state = Drive::TaskState::SHRED_ACTIVE; - //task for drive is running --> don´t show more task options - thread(ThreadShred).detach(); - } + if(getSelectedDrive()->state == Drive::TaskState::SHRED_SELECTED) + { + getSelectedDrive()->state = Drive::TaskState::SHRED_ACTIVE; + //task for drive is running --> don´t show more task options + thread(ThreadShred).detach(); + } - if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) - { - SELECTED_DRIVE.state = Drive::TaskState::DELETE_ACTIVE; - //task for drive is running --> don´t show more task options - Delete::deleteDrive(&SELECTED_DRIVE); //blocking, no thread - SELECTED_DRIVE.state = Drive::TaskState::NONE; //delete finished - SELECTED_DRIVE.bWasDeleteted = true; + if(getSelectedDrive()->state == Drive::TaskState::DELETE_SELECTED) + { + getSelectedDrive()->state = Drive::TaskState::DELETE_ACTIVE; + //task for drive is running --> don´t show more task options + Delete::deleteDrive(getSelectedDrive()); //blocking, no thread + getSelectedDrive()->state = Drive::TaskState::NONE; //delete finished + getSelectedDrive()->bWasDeleteted = true; + } } } void reHDD::handleESC() { - if(SELECTED_DRIVE.state == Drive::TaskState::SHRED_SELECTED) + if (getSelectedDrive() != nullptr) { - SELECTED_DRIVE.state = Drive::TaskState::NONE; - //task for drive is selected --> remove selection - } + if(getSelectedDrive()->state == Drive::TaskState::SHRED_SELECTED) + { + getSelectedDrive()->state = Drive::TaskState::NONE; + //task for drive is selected --> remove selection + } - if(SELECTED_DRIVE.state == Drive::TaskState::DELETE_SELECTED) - { - SELECTED_DRIVE.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(SELECTED_DRIVE.state == Drive::SHRED_ACTIVE || SELECTED_DRIVE.state == Drive::DELETE_ACTIVE ) + if (getSelectedDrive() != nullptr) { - SELECTED_DRIVE.state = Drive::NONE; - //task for drive is running --> remove selection + if(getSelectedDrive()->state == Drive::SHRED_ACTIVE || getSelectedDrive()->state == Drive::DELETE_ACTIVE ) + { + getSelectedDrive()->state = Drive::NONE; + //task for drive is running --> remove selection + } } } diff --git a/src/tui.cpp b/src/tui.cpp index b6521bd..ff2fcc9 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -51,7 +51,7 @@ void TUI::initTUI() mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); } -void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) +void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) { int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -79,10 +79,10 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) bool bSelectedEntry = false; - if(i32SelectedEntry == (it - pvecDrives->begin())) + if(u8SelectedEntry == (it - pvecDrives->begin())) { bSelectedEntry = true; //mark this drive in entries list - displaySelectedDrive(pvecDrives->at(i32SelectedEntry), stdscrX, stdscrY); + displaySelectedDrive(pvecDrives->at(u8SelectedEntry), stdscrX, stdscrY); } @@ -114,6 +114,23 @@ void TUI::updateTUI(vector * pvecDrives, int32_t i32SelectedEntry) WINDOW * tmp = createEntryWindow( ((int)(stdscrX/3) - 2), 5, 3, (5* (it - pvecDrives->begin()) )+3, sModelFamily, sModelName, sCapacity, sState, bSelectedEntry); wrefresh(tmp); + }//end loop though drives + + if(pvecDrives->size() == 0) + { + //no selected drive present + struct MenuState menustate; + menustate.bAbort = false; + menustate.bConfirmDelete = false; + menustate.bConfirmShred = false; + menustate.bDelete = false; + menustate.bShred = false; + + menuview=createMenuView(((stdscrX)-(int)(stdscrX/3)-7), 10, (int)(stdscrX/3)+5,(stdscrY-11), menustate); + wrefresh(menuview); + + detailview=overwriteDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5); + wrefresh(detailview); } } @@ -183,6 +200,7 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive newWindow = newwin(iYSize, iXSize, 2, iXStart); wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_DETAIL)); box(newWindow, ACS_VLINE, ACS_HLINE); + string title = "Selected Drive: " + drive.getModelName() + " " + drive.sCapacityToText(); centerTitle(newWindow, title.c_str()); @@ -242,6 +260,40 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive return newWindow; } +WINDOW* TUI::overwriteDetailViewWindow( int iXSize, int iYSize, int iXStart) +{ + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, 2, iXStart); + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_DETAIL)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + string title = "About this tool"; + centerTitle(newWindow, title.c_str()); + + string sLine01 = "Path: NextLine "; + string sLine02 = "Path: NextLine "; + string sLine03 = "Path: NextLine "; + string sLine04 = "Path: NextLine "; + string sLine05 = "Path: NextLine "; + string sLine06 = "Path: NextLine "; + string sLine07 = "Path: NextLine "; + + uint16_t u16Line = 5; + + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine01.size()/2), sLine01.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine02.size()/2), sLine02.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine03.size()/2), sLine03.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine04.size()/2), sLine04.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine05.size()/2), sLine05.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine06.size()/2), sLine06.c_str()); + mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine07.size()/2), sLine07.c_str()); + + attroff(COLOR_PAIR(COLOR_AREA_DETAIL)); + + keypad(newWindow, TRUE); + return newWindow; +} + WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected) { WINDOW *newWindow; diff --git a/vcCodium.code-workspace b/vcCodium.code-workspace index 9feb538..9e8c99c 100644 --- a/vcCodium.code-workspace +++ b/vcCodium.code-workspace @@ -60,7 +60,8 @@ "stdexcept": "cpp", "stop_token": "cpp", "streambuf": "cpp", - "typeinfo": "cpp" + "typeinfo": "cpp", + "iomanip": "cpp" }, "git.ignoreLimitWarning": true } From b271f7955a862b7588e6638271655fbfeefb44b4 Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 28 Aug 2020 15:28:57 +0200 Subject: [PATCH 25/41] percent are now two digit precision --- include/drive.h | 6 +++--- include/reHDD.h | 6 +----- include/shred/shred.h | 2 +- src/drive.cpp | 10 +++++----- src/shred/shred.cpp | 23 +++++++++++++++-------- src/tui.cpp | 19 +++++++++++-------- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/include/drive.h b/include/drive.h index efe38fc..33cd10d 100644 --- a/include/drive.h +++ b/include/drive.h @@ -34,7 +34,7 @@ private: uint32_t u32PowerOnHours = 0U; //in hours uint32_t u32PowerCycles = 0U; - uint8_t u8TaskPercentage = 0U; //in percent for Shred (1 to 100) + double d32TaskPercentage = 0U; //in percent for Shred (1 to 100) protected: @@ -66,8 +66,8 @@ public: string sPowerOnHoursToText(); string sPowerCyclesToText(); - void setTaskPercentage(uint8_t u8TaskPercentage); - uint8_t getTaskPercentage(void); + void setTaskPercentage(double d32TaskPercentage); + double getTaskPercentage(void); }; diff --git a/include/reHDD.h b/include/reHDD.h index 79e6f5a..44c260d 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,17 +8,13 @@ #ifndef REHDD_H_ #define REHDD_H_ -#define DRYRUN +//#define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 4000 //mark drive if at this limit or beyond #define SHRED_ITERATIONS 1 - - - - #define READ 0 #define WRITE 1 diff --git a/include/shred/shred.h b/include/shred/shred.h index b1efaa1..c1d1b22 100644 --- a/include/shred/shred.h +++ b/include/shred/shred.h @@ -80,7 +80,7 @@ public: private: Shred(void); - static inline uint8_t calcProgress(); + static inline double calcProgress(); static inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); static inline void tfnge_init(struct tfnge_stream *tfe, const void *key); static inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); diff --git a/src/drive.cpp b/src/drive.cpp index ebea0e8..18441ff 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -86,16 +86,16 @@ string Drive::sPowerCyclesToText() return to_string(getPowerCycles()); } -void Drive::setTaskPercentage(uint8_t u8TaskPercentage) +void Drive::setTaskPercentage(double d32TaskPercentage) { - if(u8TaskPercentage <= 100) + if(d32TaskPercentage <= 100) { - this->u8TaskPercentage = u8TaskPercentage; + this->d32TaskPercentage = d32TaskPercentage; } } -uint8_t Drive::getTaskPercentage(void) +double Drive::getTaskPercentage(void) { - return this->u8TaskPercentage; + return this->d32TaskPercentage; } diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index 605f2a3..d896f43 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -24,7 +24,7 @@ static struct tfnge_stream tfnge; static unsigned long blockcount = 0UL; static long blockcount_max; -static uint8_t u8Percent; +static double d32Percent; #endif /** * \brief shred drive with shred @@ -60,7 +60,7 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) blockcount_max = SHRED_ITERATIONS*(drive->getCapacity()/4096); - u8Percent = 0U; + d32Percent = 0U; rsf = open(randsrc, O_RDONLY | O_LARGEFILE); if (rsf == -1) @@ -148,19 +148,19 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) // write block loop while (1) { - usleep(10); + //usleep(10); if(drive->state != Drive::SHRED_ACTIVE) { goto _return; } - uint8_t u8TmpPercent = calcProgress(); + double d32TmpPercent = calcProgress(); - if(u8Percent != u8TmpPercent) + if((d32TmpPercent-d32Percent) >= 0.09) { - drive->setTaskPercentage(u8TmpPercent); - u8Percent = u8TmpPercent; + drive->setTaskPercentage(d32TmpPercent); + d32Percent = d32TmpPercent; write(*ipSignalFd, "A",1); } @@ -267,11 +267,18 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) _return: optind++; close(rsf); + + if(drive->state == Drive::SHRED_ACTIVE) + { + drive->bWasShredded = true; + } + + #endif } #ifndef DRYRUN -uint8_t Shred::calcProgress() +double Shred::calcProgress() { blockcount++; return ((((double)blockcount/(double)blockcount_max))*100); diff --git a/src/tui.cpp b/src/tui.cpp index ff2fcc9..4dd0c29 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -85,11 +85,14 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) displaySelectedDrive(pvecDrives->at(u8SelectedEntry), stdscrX, stdscrY); } + stringstream stream; switch (it->state) { case Drive::SHRED_ACTIVE: - sState = "Shredding: " + to_string(it->getTaskPercentage()) + "%"; + + stream << fixed << setprecision(2) << (it->getTaskPercentage()); + sState = "Shredding: " + stream.str() + "%"; break; case Drive::DELETE_ACTIVE: @@ -190,7 +193,7 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) centerTitle(newWindow, "Detected Drives"); - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -256,7 +259,7 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive mvwaddstr(newWindow,u16Line++, 3, sErrorCount.c_str()); } - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -290,7 +293,7 @@ WINDOW* TUI::overwriteDetailViewWindow( int iXSize, int iYSize, int iXStart) attroff(COLOR_PAIR(COLOR_AREA_DETAIL)); - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -320,7 +323,7 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, mvwaddstr(newWindow,2, iXSize-sState.length()-5, sState.c_str()); - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -346,7 +349,7 @@ WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart) mvwaddstr(newWindow,2, 2, time.c_str()); - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -375,7 +378,7 @@ WINDOW* TUI::createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, st { mvwaddstr(newWindow,u16Line++, 3, "Press D for Delete"); } - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } @@ -393,7 +396,7 @@ WINDOW* TUI::createDialog(int iXSize, int iYSize, int iXStart, int iYStart, stri mvwaddstr(newWindow,u16Line++, 3, optionA.c_str()); mvwaddstr(newWindow,u16Line++, 3, optionB.c_str()); - keypad(newWindow, TRUE); + //keypad(newWindow, TRUE); return newWindow; } From 7c00fd638cc1c8ddd337ab2a37b6c202a039f577 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 30 Aug 2020 12:03:37 +0200 Subject: [PATCH 26/41] fixed bug #15 --- include/reHDD.h | 4 +--- src/reHDD.cpp | 16 +--------------- src/shred/shred.cpp | 14 +++++++------- src/tui.cpp | 2 +- 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 44c260d..fe7a087 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,7 +8,7 @@ #ifndef REHDD_H_ #define REHDD_H_ -//#define DRYRUN +#define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 4000 //mark drive if at this limit or beyond @@ -72,9 +72,7 @@ private: static void handleEnter(); static void handleESC(); static void handleAbort(); - static void checkShredComplete(vector * pvecDrives); static Drive* getSelectedDrive(); - }; diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 47f12c5..5e97f98 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -75,8 +75,6 @@ void reHDD::app_logic(void) { char dummy; read (fdShredInformPipe[0],&dummy,1); - - checkShredComplete(&vecDrives); } ui->updateTUI(&vecDrives, u8SelectedEntry); } //endless loop @@ -457,17 +455,5 @@ void reHDD::handleAbort() } } -void reHDD::checkShredComplete(vector * pvecDrives) -{ - vector ::iterator it; - for (it = pvecDrives->begin(); it != pvecDrives->end(); ++it) - { - if(it->getTaskPercentage() == 100 ) - { - it->bWasShredded = true; //mark this drive as shredded - it->setTaskPercentage(0); //reset for an other shredding - it->state = Drive::NONE; //reset for an other task - } - } -} + diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index d896f43..8abbeb1 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -34,16 +34,16 @@ static double d32Percent; void Shred::shredDrive(Drive* drive, int* ipSignalFd) { #ifdef DRYRUN - for(int i = 0; i<=10; i++) + for(int i = 0; i<=100; i++) { if(drive->state != Drive::SHRED_ACTIVE) { return; } - drive->setTaskPercentage(i*10); + drive->setTaskPercentage(i+0.05); write(*ipSignalFd, "A",1); - sleep(2); + usleep(20000); } #endif @@ -152,6 +152,7 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) if(drive->state != Drive::SHRED_ACTIVE) { + drive->setTaskPercentage(0); goto _return; } @@ -267,14 +268,13 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) _return: optind++; close(rsf); - +#endif if(drive->state == Drive::SHRED_ACTIVE) { drive->bWasShredded = true; + drive->state= Drive::NONE; + drive->setTaskPercentage(0); } - - -#endif } #ifndef DRYRUN diff --git a/src/tui.cpp b/src/tui.cpp index 4dd0c29..439b654 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -88,7 +88,7 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) stringstream stream; switch (it->state) - { + { case Drive::SHRED_ACTIVE: stream << fixed << setprecision(2) << (it->getTaskPercentage()); From 37317def9f90df8e8b1ca91ec271c5f6eb0298fe Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 30 Aug 2020 15:38:14 +0200 Subject: [PATCH 27/41] added mutex for ui refresh --- src/tui.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tui.cpp b/src/tui.cpp index 439b654..2536b22 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -7,12 +7,11 @@ #include "../include/reHDD.h" +static std::mutex mxUIrefresh; TUI::TUI(void) { - - } @@ -53,6 +52,7 @@ void TUI::initTUI() void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) { + mxUIrefresh.lock(); int stdscrX, stdscrY; getmaxyx(stdscr, stdscrY, stdscrX); @@ -135,6 +135,8 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) detailview=overwriteDetailViewWindow(((stdscrX)-(int)(stdscrX/3)-7), (stdscrY-15), (int)(stdscrX/3)+5); wrefresh(detailview); } + + mxUIrefresh.unlock(); } enum TUI::UserInput TUI::readUserInput() From 8357332b60c1f4a297f28c52779f7d41a9a9ec71 Mon Sep 17 00:00:00 2001 From: localhorst Date: Sun, 30 Aug 2020 16:05:22 +0200 Subject: [PATCH 28/41] upadte ui after shred --- src/reHDD.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 5e97f98..051ca08 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -180,6 +180,7 @@ void reHDD::ThreadShred() if (getSelectedDrive() != nullptr) { Shred::shredDrive(getSelectedDrive(), &fdShredInformPipe[1]); + ui->updateTUI(&vecDrives, u8SelectedEntry); } } From eab95844b8c45c09020c57afcd186370d9684e25 Mon Sep 17 00:00:00 2001 From: Hendrik Schutter Date: Mon, 31 Aug 2020 09:40:31 +0200 Subject: [PATCH 29/41] changed WORSE_POWERUP --- include/reHDD.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/reHDD.h b/include/reHDD.h index fe7a087..690959d 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -11,7 +11,7 @@ #define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond -#define WORSE_POWERUP 4000 //mark drive if at this limit or beyond +#define WORSE_POWERUP 10000 //mark drive if at this limit or beyond #define SHRED_ITERATIONS 1 From 730b1205f71868034fbfa5d1e91803954c429a40 Mon Sep 17 00:00:00 2001 From: localhorst Date: Mon, 7 Sep 2020 17:23:15 +0200 Subject: [PATCH 30/41] added logging basics --- include/logger/logger.h | 80 ++++++++++++++ include/reHDD.h | 15 ++- src/logger/logger.cpp | 235 ++++++++++++++++++++++++++++++++++++++++ src/reHDD.cpp | 21 ++-- src/tui.cpp | 15 +-- 5 files changed, 344 insertions(+), 22 deletions(-) create mode 100644 include/logger/logger.h create mode 100644 src/logger/logger.cpp diff --git a/include/logger/logger.h b/include/logger/logger.h new file mode 100644 index 0000000..b6751ea --- /dev/null +++ b/include/logger/logger.h @@ -0,0 +1,80 @@ +/** + * @file logger.h + * @brief cppSimpleLogger Header + * @author hendrik schutter + * @date 04.09.2020 + */ + +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define MENU_LINE_SIZE 110 //Size of menu lines + +#ifndef LOG_PATH +//#define LOG_PATH "./test.txt" +#endif + +#ifndef DESCRIPTION +#define DESCRIPTION "Software-Name - Copyright Company 2020" //use your values here +#endif + +#ifndef DEVICE_ID +#define DEVICE_ID "Device-Name" //use your values here +#endif + +#ifndef SOFTWARE_VERSION +#define SOFTWARE_VERSION "0.1.1.8" //use your values here +#endif + +#ifndef HARDWARE_VERSION +#define HARDWARE_VERSION "7.77.9" //use your values here +#endif + +class Logger +{ +private: + string logPath; + mutex mtxLog; + + static bool instanceFlag; + static Logger *single; + + string getTimestamp(); + void writeLog(string s); + string getMacAddress(); + string padStringMenu(char cBorder, string text, uint8_t u8LineLenght); + string menuLine(char cBorder, uint8_t u8LineLenght); + Logger(); + ~Logger(); + +public: + + void info(string s); + void warning(string s); + void error(string s); + void newLine(); + + static Logger* logThis(); +}; + +#endif // LOGGER_H_ \ No newline at end of file diff --git a/include/reHDD.h b/include/reHDD.h index 690959d..03643a0 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -18,6 +18,13 @@ #define READ 0 #define WRITE 1 +// Logger Settings +#define LOG_PATH "./reHDD.log" +#define DESCRIPTION "reHDD - Copyright Hendrik Schutter 2020" +#define DEVICE_ID "generic" +#define SOFTWARE_VERSION "alpha" +#define HARDWARE_VERSION "generic" + #include #include #include @@ -43,7 +50,9 @@ using namespace std; #include "shred/shred.h" #include "delete.h" #include "tui.h" +#include "logger/logger.h" +extern Logger* logging; template T* iterator_to_pointer(I i) { @@ -56,10 +65,12 @@ protected: public: reHDD(void); - void app_logic(); + static void app_logic(); private: + //static Logger* logging; + static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); static void filterIgnoredDrives(vector * pvecDrives); @@ -76,4 +87,4 @@ private: }; -#endif // REHDD_H_ \ No newline at end of file +#endif // REHDD_H_ diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 0000000..5708359 --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,235 @@ +/** + * @file logger.cpp + * @brief cppSimpleLogger implementation + * @author hendrik schutter + * @date 04.09.2020 + */ + + +#include "../../include/reHDD.h" //for logger settings +#include "../../include/logger/logger.h" + +using namespace std; + +string version = "0.2.1"; //logger version + +bool Logger::instanceFlag = false; +Logger* Logger::single = NULL; + +/** + * \brief create new logger instance + * \param path to log file + * \param struct with data + * \return instance of Logger + */ +Logger::Logger() +{ + this->logPath = LOG_PATH; + + writeLog(menuLine('+', MENU_LINE_SIZE)); + writeLog(padStringMenu('+', " ", MENU_LINE_SIZE)); + + writeLog(padStringMenu('+', ("Device: " + string(DEVICE_ID) + " -- " + string(DESCRIPTION)), MENU_LINE_SIZE)); + writeLog(padStringMenu('+', " ", MENU_LINE_SIZE)); + + writeLog(padStringMenu('+', ("Software ID: " + string(SOFTWARE_VERSION) + " -- Build time: " + __DATE__ + " " + __TIME__), MENU_LINE_SIZE)); + writeLog(padStringMenu('+', " ", MENU_LINE_SIZE)); + + writeLog(padStringMenu('+', ("Hardware ID: " + string(HARDWARE_VERSION) + " -- MAC: " + getMacAddress()), MENU_LINE_SIZE)); + writeLog(padStringMenu('+', " ", MENU_LINE_SIZE)); + + writeLog(padStringMenu('+', ("cppSimpleLogger -- available from https://git.mosad.xyz/localhorst/cppSimpleLogger -- Version: " + version), MENU_LINE_SIZE)); + writeLog(padStringMenu('+', " ", MENU_LINE_SIZE)); + + writeLog(menuLine('+', MENU_LINE_SIZE)); + newLine(); + info("Created new log file"); + newLine(); +} + +/** + * \brief deconstructor + * \return void + */ +Logger::~Logger() +{ + instanceFlag = false; +} + +/** + * \brief log info + * \param log-text as string + * \return void + */ +void Logger::info(string s) +{ + string tmp = getTimestamp() + " [INFO] " + s; + writeLog(tmp); +} + +/** + * \brief log warning + * \param log-text as string + * \return void + */ +void Logger::warning(string s) +{ + string tmp = getTimestamp() + " [WARNING] " + s; + writeLog(tmp); +} + +/** + * \brief log error + * \param log-text as string + * \return void + */ +void Logger::error(string s) +{ + string tmp = getTimestamp() + " [ERROR] " + s; + writeLog(tmp); +} + +/** + * \brief write to log file + * \param log as string + * \return void + */ +void Logger::writeLog(string s) +{ + ofstream logFile; + Logger::mtxLog.lock(); //lock this section for other threads + logFile.open(this->logPath, ios_base::app); + + logFile << (s + "\n"); //append to existing file + + logFile.close(); + Logger::mtxLog.unlock(); //unlock this section for other threads +} + +/** + * \brief write new line to log file + * \return void + */ +void Logger::newLine() +{ + writeLog(" "); +} + +/** + * \brief get timestamp (system time) as string + * \param void + * \return string timestamp (formatted) + */ +string Logger::getTimestamp() +{ + struct tm * timeinfo; + struct timeval tv; + int millisec; + char cpDate [80]; + char buffer [120]; + + gettimeofday(&tv, NULL); + millisec = lrint(tv.tv_usec/1000.0); // Round to nearest millisec + if (millisec>=1000) // Allow for rounding up to nearest second + { + millisec -=1000; + tv.tv_sec++; + } + timeinfo = localtime(&tv.tv_sec); + strftime (cpDate,80,"%d/%m/%Y %T",timeinfo); + sprintf(buffer, "%s.%03d", cpDate, millisec); + return buffer; +} + +/** + * \brief get MAC address (system first eth0 interface) as string + * \param void + * \return string MAC address (formatted) + */ +string Logger::getMacAddress() +{ + struct ifreq ifr; + int s = socket(AF_INET, SOCK_STREAM,0); + + strcpy(ifr.ifr_name, "eth0"); + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) + { + strcpy(ifr.ifr_name, "eno1"); + + } + + unsigned char *hwaddr = (unsigned char *)ifr.ifr_hwaddr.sa_data; + char buffer [80]; + sprintf(buffer,"%02X:%02X:%02X:%02X:%02X:%02X", hwaddr[0], hwaddr[1], hwaddr[2], + hwaddr[3], hwaddr[4], hwaddr[5]); + close(s); + string tmp = buffer; + return tmp; +} + +/** + * \brief format menu text in center + * \param char for border + * \param menu text + * \param size of menu line + * \return string menu line + */ +string Logger::padStringMenu(char cBorder, string text, uint8_t u8LineLenght) +{ + string result(1,cBorder); + uint8_t u8TextSize = text.length(); + uint8_t u8Padding = ((u8LineLenght-u8TextSize)/2); + + for(uint8_t i = 0 ; i < u8Padding; i++) + { + result.append(" "); + } + + result.append(text); + + while((uint8_t)result.length() < (u8LineLenght-1)) + { + + result.append(" "); + } + + result.append(string(1, cBorder)); + return result; +} + +/** + * \brief format a separator + * \param char for border + * \param size of menu line + * \return string menu line + */ +string Logger::menuLine(char cBorder, uint8_t u8LineLenght) +{ + string result(1,cBorder); + + while((uint8_t)result.length() < u8LineLenght) + { + result.append(string(1, cBorder)); + } + return result; +} + +/** + * \brief return a instance of the logger + * \return logger obj + */ +Logger* Logger::logThis() +{ + if (!instanceFlag) + { + single = new Logger(); //create new obj + instanceFlag = true; + return single; + } + else + { + return single; //return existing obj + } +} + + diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 051ca08..185a347 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -32,7 +32,6 @@ static fd_set selectSet; */ reHDD::reHDD(void) { - cout << "created app" << endl; u8SelectedEntry = 0U; } @@ -43,7 +42,6 @@ reHDD::reHDD(void) */ void reHDD::app_logic(void) { - cout << "app logic" << endl; ui = new TUI(); ui->initTUI(); @@ -90,6 +88,7 @@ Drive* reHDD::getSelectedDrive() } else { + Logger::logThis()->warning("selected drive not present"); return {}; } } @@ -98,7 +97,6 @@ void reHDD::ThreadScannDevices() { while(true) { - // cout << "Thread" << endl; mxScannDrives.lock(); vecNewDrives.clear(); searchDrives(&vecNewDrives); //search for new drives and store them in list @@ -180,13 +178,13 @@ void reHDD::ThreadShred() if (getSelectedDrive() != nullptr) { Shred::shredDrive(getSelectedDrive(), &fdShredInformPipe[1]); + Logger::logThis()->info("Finished shred for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); ui->updateTUI(&vecDrives, u8SelectedEntry); } } void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecNewDrives) { - vector ::iterator itOld; //Iterator for current (old) drive list vector ::iterator itNew; //Iterator for new drive list that was created from to scann thread @@ -209,6 +207,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN if(bOldDriveIsOffline == true) { //cout << "offline drive found: " << itOld->getPath() << endl; + Logger::logThis()->warning("offline drive found delete for: " + itOld->getPath()); itOld->state = Drive::NONE; } } @@ -217,6 +216,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN for (long unsigned int i=0; isize(); i++) { pvecOldDrives->push_back((*pvecNewDrives)[i]); + Logger::logThis()->info("Attached drive: " + i + string("-") + pvecNewDrives->at(i).getPath()); } } @@ -263,8 +263,6 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) vector> vtlIgnoredDevices; //store drives from ingnore file - //vector vecTmpDrives - ifstream input( "ignoreDrives.conf" ); //read ingnore file for(string sLine; getline( input, sLine );) @@ -280,8 +278,6 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) sLine.erase(0, pos + sDelimiter.length()); sIgnoredDriveUUID = sLine; } //end while - //cout << "Path: " << sIgnoredDrivePath << std::endl; - //cout << "UUID: " << sIgnoredDriveUUID << std::endl; vtlIgnoredDevices.emplace_back(sIgnoredDrivePath, sIgnoredDriveUUID); //add found path and uuid from ingnore file to vector } } @@ -322,14 +318,15 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) if (get<1>(row).compare(sUUID)) //compare uuid from ignore file and uuid from drive { cout << "[ERROR] different uuid found than in ignore file:" << it->getPath() << endl; + Logger::logThis()->error("[ERROR] different uuid found than in ignore file: " + it->getPath()); exit(EXIT_FAILURE); // exit to prevent accidentally shred a system drive } else { // same uuid found than in ignore file --> ignore this drive + Logger::logThis()->info("same uuid found than in ignore file --> ignore this drive: " + it->getPath()); it = pvecDrives->erase(it); it--; - //cout << "same uuid found than in ignore file --> ignore this drive:" << it->getPath() << endl; } } } @@ -402,6 +399,8 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) u8SelectedEntry = 0; break; } + + Logger::logThis()->info("ArrowKey - selected drive: " + to_string(u8SelectedEntry)); } void reHDD::handleEnter() @@ -410,6 +409,7 @@ void reHDD::handleEnter() { 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 thread(ThreadShred).detach(); @@ -417,11 +417,13 @@ void reHDD::handleEnter() 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 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()); } } } @@ -451,6 +453,7 @@ void reHDD::handleAbort() if(getSelectedDrive()->state == Drive::SHRED_ACTIVE || getSelectedDrive()->state == Drive::DELETE_ACTIVE ) { getSelectedDrive()->state = Drive::NONE; + Logger::logThis()->info("Abort-Shred for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); //task for drive is running --> remove selection } } diff --git a/src/tui.cpp b/src/tui.cpp index 2536b22..9c4cf8c 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -11,10 +11,8 @@ static std::mutex mxUIrefresh; TUI::TUI(void) { - } - /** * \brief wipe drive with shred * \param pointer of Drive instance @@ -33,6 +31,7 @@ void TUI::initTUI() else { printf("Your terminal does not support color\n"); + Logger::logThis()->error("Your terminal does not support color"); exit(1); } clear(); @@ -48,6 +47,7 @@ void TUI::initTUI() init_pair(COLOR_AREA_DETAIL, COLOR_BLACK, COLOR_WHITE); mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); + Logger::logThis()->info("UI successfully initialized"); } void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) @@ -122,6 +122,7 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) if(pvecDrives->size() == 0) { //no selected drive present + Logger::logThis()->warning("no selected drive present"); struct MenuState menustate; menustate.bAbort = false; menustate.bConfirmDelete = false; @@ -195,7 +196,6 @@ WINDOW* TUI::createOverViewWindow( int iXSize, int iYSize) centerTitle(newWindow, "Detected Drives"); - //keypad(newWindow, TRUE); return newWindow; } @@ -261,7 +261,6 @@ WINDOW* TUI::createDetailViewWindow( int iXSize, int iYSize, int iXStart, Drive mvwaddstr(newWindow,u16Line++, 3, sErrorCount.c_str()); } - //keypad(newWindow, TRUE); return newWindow; } @@ -295,7 +294,6 @@ WINDOW* TUI::overwriteDetailViewWindow( int iXSize, int iYSize, int iXStart) attroff(COLOR_PAIR(COLOR_AREA_DETAIL)); - //keypad(newWindow, TRUE); return newWindow; } @@ -325,14 +323,11 @@ WINDOW* TUI::createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, mvwaddstr(newWindow,2, iXSize-sState.length()-5, sState.c_str()); - //keypad(newWindow, TRUE); - return newWindow; } WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart) { - WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, 2); @@ -351,7 +346,6 @@ WINDOW* TUI::createSystemStats(int iXSize, int iYSize, int iYStart) mvwaddstr(newWindow,2, 2, time.c_str()); - //keypad(newWindow, TRUE); return newWindow; } @@ -380,7 +374,7 @@ WINDOW* TUI::createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, st { mvwaddstr(newWindow,u16Line++, 3, "Press D for Delete"); } - //keypad(newWindow, TRUE); + return newWindow; } @@ -398,7 +392,6 @@ WINDOW* TUI::createDialog(int iXSize, int iYSize, int iXStart, int iYStart, stri mvwaddstr(newWindow,u16Line++, 3, optionA.c_str()); mvwaddstr(newWindow,u16Line++, 3, optionB.c_str()); - //keypad(newWindow, TRUE); return newWindow; } From 938f267813f90f9aac063e28ffeda6ba9d4c2901 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 8 Sep 2020 13:01:09 +0200 Subject: [PATCH 31/41] completed logger usage and changed shred task to non-static --- include/reHDD.h | 15 ++++++++++----- include/shred/shred.h | 20 +++++++++++++------- src/reHDD.cpp | 42 ++++++++++++++++++++++++++---------------- src/shred/shred.cpp | 39 +++++++++++++++++++++++++++------------ src/tui.cpp | 8 ++++---- 5 files changed, 80 insertions(+), 44 deletions(-) diff --git a/include/reHDD.h b/include/reHDD.h index 03643a0..04a3187 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,7 +8,7 @@ #ifndef REHDD_H_ #define REHDD_H_ -#define DRYRUN +//#define DRYRUN #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 10000 //mark drive if at this limit or beyond @@ -20,10 +20,15 @@ // Logger Settings #define LOG_PATH "./reHDD.log" -#define DESCRIPTION "reHDD - Copyright Hendrik Schutter 2020" -#define DEVICE_ID "generic" -#define SOFTWARE_VERSION "alpha" -#define HARDWARE_VERSION "generic" +#define DESCRIPTION "reHDD - Copyright Hendrik Schutter 2020" +#define DEVICE_ID "generic" +#define SOFTWARE_VERSION "alpha" +#define HARDWARE_VERSION "generic" + +#define LOG_LEVEL_HIGH //log everything, like drive scann thread +#ifndef LOG_LEVEL_HIGH +#define LOG_LEVEL_LOW //log only user actions and tasks +#endif #include #include diff --git a/include/shred/shred.h b/include/shred/shred.h index c1d1b22..dcefdfc 100644 --- a/include/shred/shred.h +++ b/include/shred/shred.h @@ -76,16 +76,22 @@ class Shred protected: public: - static void shredDrive(Drive* drive, int* ipSignalFd); + + Shred(); + ~Shred(); + void shredDrive(Drive* drive, int* ipSignalFd); private: - Shred(void); - static inline double calcProgress(); - static inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); - static inline void tfnge_init(struct tfnge_stream *tfe, const void *key); - static inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); - static inline void tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe); + unsigned long blockcount = 0UL; + long blockcount_max; + double d32Percent; + + inline double calcProgress(); + inline void tfnge_init_iv(struct tfnge_stream *tfe, const void *key, const void *iv); + inline void tfnge_init(struct tfnge_stream *tfe, const void *key); + inline void tfng_encrypt_rawblk(TFNG_UNIT_TYPE *O, const TFNG_UNIT_TYPE *I, const TFNG_UNIT_TYPE *K); + inline void tfnge_emit(void *dst, size_t szdst, struct tfnge_stream *tfe); }; diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 185a347..6053a6c 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -104,6 +104,7 @@ void reHDD::ThreadScannDevices() addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives mxScannDrives.unlock(); write(fdNewDrivesInformPipe[1], "A",1); + printDrives(&vecNewDrives); sleep(5); //sleep 5 sec } } @@ -177,8 +178,9 @@ void reHDD::ThreadShred() { if (getSelectedDrive() != nullptr) { - Shred::shredDrive(getSelectedDrive(), &fdShredInformPipe[1]); - Logger::logThis()->info("Finished shred for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); + Shred* pShredTask = new Shred(); //create new shred task + pShredTask->shredDrive(getSelectedDrive(), &fdShredInformPipe[1]); //start new shred task + delete pShredTask; //delete shred task ui->updateTUI(&vecDrives, u8SelectedEntry); } } @@ -216,7 +218,6 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN for (long unsigned int i=0; isize(); i++) { pvecOldDrives->push_back((*pvecNewDrives)[i]); - Logger::logThis()->info("Attached drive: " + i + string("-") + pvecNewDrives->at(i).getPath()); } } @@ -228,6 +229,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN void reHDD::searchDrives(vector * pvecDrives) { // cout << "search drives ..." << endl; + Logger::logThis()->info("--> search drives <--"); char * cLine = NULL; size_t len = 0; @@ -324,7 +326,9 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) else { // 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 = pvecDrives->erase(it); it--; } @@ -340,22 +344,28 @@ void reHDD::filterIgnoredDrives(vector * pvecDrives) */ void reHDD::printDrives(vector * pvecDrives) { - cout << "------------DRIVES---------------" << endl; +#ifdef LOG_LEVEL_HIGH + Logger::logThis()->info("------------DRIVES---------------"); + //cout << "------------DRIVES---------------" << endl; vector ::iterator it; for (it = pvecDrives->begin(); it != pvecDrives->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; + /* + 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;*/ + Logger::logThis()->info(to_string(it - pvecDrives->begin()) + ": " + it->getPath() + " - " + it->getModelFamily() + " - " + it->getSerial()); } - cout << "---------------------------------" << endl; + Logger::logThis()->info("---------------------------------"); + //cout << "---------------------------------" << endl; +#endif } /** @@ -400,7 +410,7 @@ void reHDD::handleArrowKey(TUI::UserInput userInput) break; } - Logger::logThis()->info("ArrowKey - selected drive: " + to_string(u8SelectedEntry)); + Logger::logThis()->info("ArrowKey - selected drive: " + to_string(u8SelectedEntry)); } void reHDD::handleEnter() diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index 8abbeb1..48c2a2c 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -22,9 +22,18 @@ struct tfnge_stream static struct tfnge_stream tfnge; -static unsigned long blockcount = 0UL; -static long blockcount_max; -static double d32Percent; +Shred::Shred() +{ + +} + + +Shred::~Shred() +{ + +} + + #endif /** * \brief shred drive with shred @@ -60,6 +69,10 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) blockcount_max = SHRED_ITERATIONS*(drive->getCapacity()/4096); +#ifdef LOG_LEVEL_HIGH + Logger::logThis()->info("Shred-Task: Max-BlockCount: " + to_string(blockcount_max) + " - Drive: " + drive->getSerial()); +#endif + d32Percent = 0U; rsf = open(randsrc, O_RDONLY | O_LARGEFILE); @@ -80,13 +93,7 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) goto _return; } if (!blksz) blksz = (size_t)st.st_blksize; - /* - if (howmany != -1) - { - l = ll = howmany; - noround = 1; - } - */ + else l = ll = st.st_size; if (l == 0 && !S_ISREG(st.st_mode)) special = 1; memset(&st, 0, sizeof(struct stat)); @@ -148,20 +155,27 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) // write block loop while (1) { - //usleep(10); - if(drive->state != Drive::SHRED_ACTIVE) { drive->setTaskPercentage(0); + d32Percent = 0.00; + blockcount = 0; + blockcount_max = 0; + Logger::logThis()->info("Aborted shred for: " + drive->getModelName() + "-" + drive->getSerial()); goto _return; } double d32TmpPercent = calcProgress(); + + if((d32TmpPercent-d32Percent) >= 0.09) { drive->setTaskPercentage(d32TmpPercent); d32Percent = d32TmpPercent; +#ifdef LOG_LEVEL_HIGH + Logger::logThis()->info("Shred-Task: BlockCount: " + to_string(blockcount) + " - progress: " + to_string(d32Percent) + " - Drive: " + drive->getSerial()); +#endif write(*ipSignalFd, "A",1); } @@ -274,6 +288,7 @@ _return: drive->bWasShredded = true; drive->state= Drive::NONE; drive->setTaskPercentage(0); + Logger::logThis()->info("Finished shred for: " + drive->getModelName() + "-" + drive->getSerial()); } } #ifndef DRYRUN diff --git a/src/tui.cpp b/src/tui.cpp index 9c4cf8c..8e011bf 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -47,7 +47,7 @@ void TUI::initTUI() init_pair(COLOR_AREA_DETAIL, COLOR_BLACK, COLOR_WHITE); mvprintw(0, 2, "reHDD - HDD refurbishing tool - GPL 3.0 "); - Logger::logThis()->info("UI successfully initialized"); + Logger::logThis()->info("UI successfully initialized"); } void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) @@ -88,7 +88,7 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) stringstream stream; switch (it->state) - { + { case Drive::SHRED_ACTIVE: stream << fixed << setprecision(2) << (it->getTaskPercentage()); @@ -122,7 +122,7 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) if(pvecDrives->size() == 0) { //no selected drive present - Logger::logThis()->warning("no selected drive present"); + Logger::logThis()->warning("no selected drive present"); struct MenuState menustate; menustate.bAbort = false; menustate.bConfirmDelete = false; @@ -137,7 +137,7 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) wrefresh(detailview); } - mxUIrefresh.unlock(); + mxUIrefresh.unlock(); } enum TUI::UserInput TUI::readUserInput() From cb361acfd4d1d9afadf624456b3c83c68626a02f Mon Sep 17 00:00:00 2001 From: localhorst Date: Wed, 9 Sep 2020 16:11:25 +0200 Subject: [PATCH 32/41] fixed bug with filtering new attached drives --- include/drive.h | 2 ++ include/reHDD.h | 2 -- src/reHDD.cpp | 74 +++++++++++++++++++++++++++++++++------------ src/shred/shred.cpp | 2 +- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/include/drive.h b/include/drive.h index 33cd10d..5f7063a 100644 --- a/include/drive.h +++ b/include/drive.h @@ -23,6 +23,7 @@ public: bool bWasShredded = false; bool bWasDeleteted = false; + bool bIsOffline = false; private: string sPath; @@ -36,6 +37,7 @@ private: double d32TaskPercentage = 0U; //in percent for Shred (1 to 100) + protected: public: diff --git a/include/reHDD.h b/include/reHDD.h index 04a3187..b1defd5 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -74,8 +74,6 @@ public: private: - //static Logger* logging; - static void searchDrives(vector * pvecDrives); static void printDrives(vector * pvecDrives); static void filterIgnoredDrives(vector * pvecDrives); diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 6053a6c..1b55a6e 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -13,7 +13,7 @@ static int fdShredInformPipe[2];//File descriptor for pipe that informs if a wip static std::mutex mxScannDrives; -static vector vecNewDrives; //store found drives that are updated every 5sec +vector vecNewDrives; //store found drives that are updated every 5sec static vector vecDrives; //stores all drive data from scann thread @@ -33,6 +33,8 @@ static fd_set selectSet; reHDD::reHDD(void) { u8SelectedEntry = 0U; + + vecDrives.reserve(128); } /** @@ -64,8 +66,8 @@ void reHDD::app_logic(void) char dummy; read (fdNewDrivesInformPipe[0],&dummy,1); mxScannDrives.lock(); - filterNewDrives(&vecDrives, &vecNewDrives); //filter and copy to app logic vector + printDrives(&vecDrives); mxScannDrives.unlock(); } @@ -104,7 +106,6 @@ void reHDD::ThreadScannDevices() addSMARTData(&vecNewDrives); //add S.M.A.R.T. Data to the drives mxScannDrives.unlock(); write(fdNewDrivesInformPipe[1], "A",1); - printDrives(&vecNewDrives); sleep(5); //sleep 5 sec } } @@ -190,35 +191,64 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN vector ::iterator itOld; //Iterator for current (old) drive list vector ::iterator itNew; //Iterator for new drive list that was created from to scann thread + //remove offline old drives from previously run + for (itOld = pvecOldDrives->begin(); itOld != pvecOldDrives->end();) + { + if(itOld->bIsOffline == true) + { + Logger::logThis()->warning("Offline drive found: " + itOld->getPath()); + itOld = pvecOldDrives->erase(itOld); + /* + if(pvecOldDrives->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 = pvecOldDrives->begin(); itOld != pvecOldDrives->end(); ++itOld) { - bool bOldDriveIsOffline = true; - for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end(); ++itNew) + itOld->bIsOffline = true; //set offline befor seachring in the new list + for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end();) { if(itOld->getSerial() == itNew->getSerial()) { - bOldDriveIsOffline = false; - // copy old drive instance date in new instance - itNew->state = itOld->state; //copy state - itNew->setTaskPercentage(itOld->getTaskPercentage()); //copy percentage - itNew->bWasDeleteted = itOld->bWasDeleteted; //copy finished task delete - itNew->bWasShredded = itOld->bWasShredded; //copy finished task shred + itOld->bIsOffline = false; //drive is still attached +#ifdef LOG_LEVEL_HIGH + Logger::logThis()->info("Delete new drive, because allready attached: " + itNew->getModelName()); +#endif + itNew = pvecNewDrives->erase(itNew); //This drive is allready attached, remove from new list + } + else + { + ++itNew; } } + } - if(bOldDriveIsOffline == true) + //mark offline old drives + for (itOld = pvecOldDrives->begin(); itOld != pvecOldDrives->end(); ++itOld) + { + if(itOld->bIsOffline == true) { //cout << "offline drive found: " << itOld->getPath() << endl; - Logger::logThis()->warning("offline drive found delete for: " + itOld->getPath()); - itOld->state = Drive::NONE; + Logger::logThis()->warning("Mark offline drive found: " + itOld->getPath()); + itOld->state = Drive::NONE; //clear state --> shred task will terminate } } - pvecOldDrives->clear(); - for (long unsigned int i=0; isize(); i++) + //add new drives to drive list + for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end(); ++itNew) { - pvecOldDrives->push_back((*pvecNewDrives)[i]); + pvecOldDrives->push_back(pvecNewDrives->at(itNew - pvecNewDrives->begin())); + Logger::logThis()->info("Add new drive: " + itNew->getModelName()); } + pvecNewDrives->clear(); } /** @@ -246,6 +276,7 @@ void reHDD::searchDrives(vector * pvecDrives) { Drive* tmpDrive = new Drive(string(cLine).substr (2,8)); tmpDrive->state = Drive::NONE; + tmpDrive->bIsOffline = false; pvecDrives->push_back(*tmpDrive); } } @@ -361,7 +392,12 @@ void reHDD::printDrives(vector * pvecDrives) cout << "PowerCycle: " << it->getPowerCycles() << endl; cout << "ErrorCount: " << it->getErrorCount() << endl; cout << endl;*/ - Logger::logThis()->info(to_string(it - pvecDrives->begin()) + ": " + it->getPath() + " - " + it->getModelFamily() + " - " + it->getSerial()); + + ostringstream address; + address << (void const *)&pvecDrives->at(it - pvecDrives->begin()); + + + Logger::logThis()->info(to_string(it - pvecDrives->begin()) + ": " + it->getPath() + " - " + it->getModelFamily() + " - " + it->getSerial() + " @" + address.str()); } Logger::logThis()->info("---------------------------------"); //cout << "---------------------------------" << endl; @@ -463,7 +499,7 @@ void reHDD::handleAbort() if(getSelectedDrive()->state == Drive::SHRED_ACTIVE || getSelectedDrive()->state == Drive::DELETE_ACTIVE ) { getSelectedDrive()->state = Drive::NONE; - Logger::logThis()->info("Abort-Shred for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); + Logger::logThis()->info("Abort-Shred-Signal for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); //task for drive is running --> remove selection } } diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index 48c2a2c..d5b3969 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -288,7 +288,7 @@ _return: drive->bWasShredded = true; drive->state= Drive::NONE; drive->setTaskPercentage(0); - Logger::logThis()->info("Finished shred for: " + drive->getModelName() + "-" + drive->getSerial()); + Logger::logThis()->info("Finished shred for: " + drive->getModelName() + "-" + drive->getSerial()); } } #ifndef DRYRUN From 47043afb837ab168f454252ef5471a12a8dcdf02 Mon Sep 17 00:00:00 2001 From: localhorst Date: Thu, 10 Sep 2020 12:50:18 +0200 Subject: [PATCH 33/41] added about tool text --- out.txt | 1 - src/tui.cpp | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 out.txt diff --git a/out.txt b/out.txt deleted file mode 100644 index 60fffd1..0000000 --- a/out.txt +++ /dev/null @@ -1 +0,0 @@ -date diff --git a/src/tui.cpp b/src/tui.cpp index 8e011bf..90cc902 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -274,23 +274,23 @@ WINDOW* TUI::overwriteDetailViewWindow( int iXSize, int iYSize, int iXStart) string title = "About this tool"; centerTitle(newWindow, title.c_str()); - string sLine01 = "Path: NextLine "; - string sLine02 = "Path: NextLine "; - string sLine03 = "Path: NextLine "; - string sLine04 = "Path: NextLine "; - string sLine05 = "Path: NextLine "; - string sLine06 = "Path: NextLine "; - string sLine07 = "Path: NextLine "; - + string sLine01 = "reHDD - hard drive refurbishing tool"; + string sLine02 = "Version: beta X.X.X"; + string sLine03 = "Available under GPL 3.0"; + string sLine04 = "https://git.mosad.xyz/localhorst/reHDD"; + string sLine05 = "Delete: Wipe format table - this is NOT secure"; + string sLine06 = "Shred: Overwite drive " + to_string(SHRED_ITERATIONS) + " iterations - this is secure"; + uint16_t u16Line = 5; mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine01.size()/2), sLine01.c_str()); mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine02.size()/2), sLine02.c_str()); + u16Line++; mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine03.size()/2), sLine03.c_str()); mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine04.size()/2), sLine04.c_str()); + u16Line++; mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine05.size()/2), sLine05.c_str()); mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine06.size()/2), sLine06.c_str()); - mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine07.size()/2), sLine07.c_str()); attroff(COLOR_PAIR(COLOR_AREA_DETAIL)); @@ -450,4 +450,4 @@ void TUI::displaySelectedDrive(Drive drive, int stdscrX, int stdscrY) { delwin(dialog); } -} \ No newline at end of file +} From 5f593682e2f254d9f683edbbf4e9e80045e666b2 Mon Sep 17 00:00:00 2001 From: localhorst Date: Thu, 10 Sep 2020 12:51:07 +0200 Subject: [PATCH 34/41] updated gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d9710be..ee41a9d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ reHDD +reHDD.log From 67b8e302be7e319374e404a143c92324c0e403f4 Mon Sep 17 00:00:00 2001 From: localhorst Date: Fri, 11 Sep 2020 17:18:53 +0200 Subject: [PATCH 35/41] detect frozen drives and show that the user --- include/drive.h | 8 ++++++-- include/reHDD.h | 13 ++++++++----- include/tui.h | 3 ++- src/drive.cpp | 21 +++++++++++++++++++++ src/reHDD.cpp | 27 ++++++++++++++++++++++++--- src/shred/shred.cpp | 4 ++-- src/tui.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 103 insertions(+), 15 deletions(-) diff --git a/include/drive.h b/include/drive.h index 5f7063a..e3862f7 100644 --- a/include/drive.h +++ b/include/drive.h @@ -18,7 +18,8 @@ public: SHRED_SELECTED, SHRED_ACTIVE, DELETE_SELECTED, - DELETE_ACTIVE + DELETE_ACTIVE, + FROZEN } state; bool bWasShredded = false; @@ -34,9 +35,11 @@ private: uint32_t u32ErrorCount = 0U; uint32_t u32PowerOnHours = 0U; //in hours uint32_t u32PowerCycles = 0U; - + time_t u32Timestamp = 0U; //unix timestamp for detecting a frozen drive double d32TaskPercentage = 0U; //in percent for Shred (1 to 100) +private: + void setTimestamp(); protected: @@ -54,6 +57,7 @@ public: uint32_t getErrorCount(void); uint32_t getPowerOnHours(void); //in hours uint32_t getPowerCycles(void); + void checkFrozenDrive(void); void setDriveSMARTData( string modelFamily, string modelName, diff --git a/include/reHDD.h b/include/reHDD.h index b1defd5..5d6de82 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,15 +8,13 @@ #ifndef REHDD_H_ #define REHDD_H_ -//#define DRYRUN +#define DRYRUN +// Drive handling Settings #define WORSE_HOURS 19200 //mark drive if at this limit or beyond #define WORSE_POWERUP 10000 //mark drive if at this limit or beyond - #define SHRED_ITERATIONS 1 - -#define READ 0 -#define WRITE 1 +#define FROZEN_TIMEOUT 5 //After this timeout (minutes) the drive will be marked as frozen // Logger Settings #define LOG_PATH "./reHDD.log" @@ -30,6 +28,10 @@ #define LOG_LEVEL_LOW //log only user actions and tasks #endif +//IPC pipes +#define READ 0 +#define WRITE 1 + #include #include #include @@ -82,6 +84,7 @@ private: static void ThreadScannDevices(); static void ThreadUserInput(); static void ThreadShred(); + static void ThreadCheckFrozenDrives(); static void handleArrowKey(TUI::UserInput userInput); static void handleEnter(); static void handleESC(); diff --git a/include/tui.h b/include/tui.h index 3d4c3a9..135ff0d 100644 --- a/include/tui.h +++ b/include/tui.h @@ -56,10 +56,11 @@ private: 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,string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected); + static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, string sModelFamily, string sModelName, string sCapacity, string sState, bool bSelected); static WINDOW *createSystemStats(int iXSize, int iYSize, 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, string selectedTask, string optionA, string optionB); + static WINDOW* createFrozenWarning(int iXSize, int iYSize, int iXStart, int iYStart, string sPath, string sModelFamily, string sModelName, string sSerial); void displaySelectedDrive(Drive drive, int stdscrX, int stdscrY); diff --git a/src/drive.cpp b/src/drive.cpp index 18441ff..36cd7c9 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -91,6 +91,7 @@ void Drive::setTaskPercentage(double d32TaskPercentage) if(d32TaskPercentage <= 100) { this->d32TaskPercentage = d32TaskPercentage; + this->setTimestamp(); //set timestamp for this progress for detecting a frozen drive } } double Drive::getTaskPercentage(void) @@ -125,4 +126,24 @@ void Drive::setDriveSMARTData( string modelFamily, u32ErrorCount = errorCount; u32PowerOnHours = powerOnHours; u32PowerCycles = powerCycle; +} + + +void Drive::setTimestamp() +{ + time(&this->u32Timestamp); +} + +void Drive::checkFrozenDrive(void) +{ + time_t u32localtime; + time(&u32localtime); + + if((u32localtime - this->u32Timestamp) >= (FROZEN_TIMEOUT*60) && (this->u32Timestamp > 0)) + { + Logger::logThis()->warning("Drive Frozen: " + this->getModelName() + " " + this->getSerial()); + this->bWasDeleteted = false; + this->bWasShredded = false; + this->state = Drive::FROZEN; + } } \ No newline at end of file diff --git a/src/reHDD.cpp b/src/reHDD.cpp index 1b55a6e..fbc8d96 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -52,6 +52,7 @@ void reHDD::app_logic(void) 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) { @@ -60,15 +61,14 @@ void reHDD::app_logic(void) FD_SET(fdShredInformPipe[0], &selectSet); select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); - + mxScannDrives.lock(); if( FD_ISSET(fdNewDrivesInformPipe[0], &selectSet)) { char dummy; read (fdNewDrivesInformPipe[0],&dummy,1); - mxScannDrives.lock(); filterNewDrives(&vecDrives, &vecNewDrives); //filter and copy to app logic vector printDrives(&vecDrives); - mxScannDrives.unlock(); + } if (FD_ISSET(fdShredInformPipe[0], &selectSet)) @@ -77,9 +77,11 @@ void reHDD::app_logic(void) read (fdShredInformPipe[0],&dummy,1); } ui->updateTUI(&vecDrives, u8SelectedEntry); + mxScannDrives.unlock(); } //endless loop thDevices.join(); thUserInput.join(); + thCheckFrozenDrives.join(); } Drive* reHDD::getSelectedDrive() @@ -110,6 +112,25 @@ void reHDD::ThreadScannDevices() } } + + +void reHDD::ThreadCheckFrozenDrives() +{ + while(true) + { + mxScannDrives.lock(); + for(auto it = begin(vecDrives); it != end(vecDrives); ++it) + { + if(it->state == Drive::SHRED_ACTIVE) + { + it->checkFrozenDrive(); + } + } + mxScannDrives.unlock(); + sleep(5); //sleep 5 sec + } +} + void reHDD::ThreadUserInput() { while(true) diff --git a/src/shred/shred.cpp b/src/shred/shred.cpp index d5b3969..7f9377b 100644 --- a/src/shred/shred.cpp +++ b/src/shred/shred.cpp @@ -21,6 +21,7 @@ struct tfnge_stream }; static struct tfnge_stream tfnge; +#endif Shred::Shred() { @@ -34,7 +35,7 @@ Shred::~Shred() } -#endif + /** * \brief shred drive with shred * \param pointer of Drive instance @@ -51,7 +52,6 @@ void Shred::shredDrive(Drive* drive, int* ipSignalFd) } drive->setTaskPercentage(i+0.05); write(*ipSignalFd, "A",1); - usleep(20000); } #endif diff --git a/src/tui.cpp b/src/tui.cpp index 90cc902..e9d63fa 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -111,6 +111,10 @@ void TUI::updateTUI(vector * pvecDrives, uint8_t u8SelectedEntry) sState = "SHREDDED"; //mark drive as shreded previously, overwrite if deleted } break; + case Drive::FROZEN: + dialog=createFrozenWarning(70, 16, ((stdscrX)-(int)(stdscrX/2)-35),(int)(stdscrY/2)-8, it->getPath(), it->getModelFamily(), it->getModelName(), it->getSerial()); + wrefresh(dialog); + break; default: break; } @@ -276,11 +280,11 @@ WINDOW* TUI::overwriteDetailViewWindow( int iXSize, int iYSize, int iXStart) string sLine01 = "reHDD - hard drive refurbishing tool"; string sLine02 = "Version: beta X.X.X"; - string sLine03 = "Available under GPL 3.0"; + string sLine03 = "Available under GPL 3.0"; string sLine04 = "https://git.mosad.xyz/localhorst/reHDD"; string sLine05 = "Delete: Wipe format table - this is NOT secure"; string sLine06 = "Shred: Overwite drive " + to_string(SHRED_ITERATIONS) + " iterations - this is secure"; - + uint16_t u16Line = 5; mvwaddstr(newWindow,u16Line++, (iXSize/2)-(sLine01.size()/2), sLine01.c_str()); @@ -395,6 +399,40 @@ WINDOW* TUI::createDialog(int iXSize, int iYSize, int iXStart, int iYStart, stri return newWindow; } +WINDOW* TUI::createFrozenWarning(int iXSize, int iYSize, int iXStart, int iYStart, string sPath, string sModelFamily, string sModelName, string sSerial) +{ + WINDOW *newWindow; + newWindow = newwin(iYSize, iXSize, iYStart, iXStart); + + wbkgd(newWindow, COLOR_PAIR(COLOR_AREA_ENTRY_SELECTED)); + box(newWindow, ACS_VLINE, ACS_HLINE); + + string sHeader = "Drive " + sPath + " is frozen"; + string sLine01 = "Please detach this drive and check it manually:"; + string sLinePath = "Path: " +sPath; + string sLineModelFamlily = "ModelFamily: " + sModelFamily; + string sLineModelName = "ModelName: " + sModelName; + string sLineSerial = "Serial: " + sSerial; + + string sLine02 = "reHDD was not able to write data to the drive."; + string sLine03 = "This can be caused by an malfunctioning drive."; + + centerTitle(newWindow, sHeader.c_str()); + + uint16_t u16Line = 2; + mvwaddstr(newWindow,u16Line++, 3, sLine01.c_str()); + u16Line++; + mvwaddstr(newWindow,u16Line++, 3, sLinePath.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sLineModelFamlily.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sLineModelName.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sLineSerial.c_str()); + u16Line++; + mvwaddstr(newWindow,u16Line++, 3, sLine02.c_str()); + mvwaddstr(newWindow,u16Line++, 3, sLine03.c_str()); + + return newWindow; +} + void TUI::displaySelectedDrive(Drive drive, int stdscrX, int stdscrY) { struct MenuState menustate; From 5a0f92deec0eb0c3ae91e3db9db81f38e40e9239 Mon Sep 17 00:00:00 2001 From: Hendrik Schutter Date: Tue, 15 Sep 2020 00:00:17 +0200 Subject: [PATCH 36/41] added debian install notes --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index e9642f5..6b267fe 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,11 @@ * Check S.M.A.R.T. values and make an 'passed' or 'not passed' decision * If passed, wipe the data securely +## Debian Install Notes + +* apt-get install ncurses-dev +* apt-get install hwinfo +* wget http://ftp.de.debian.org/debian/pool/main/s/smartmontools/smartmontools_7.1-1_amd64.deb +* dpkg --install smartmontools_7.1-1_amd64.deb + + From 5e74a99062242db84ea381d7225f0a35630111e3 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 15 Sep 2020 11:11:53 +0200 Subject: [PATCH 37/41] detect same drive with serial and path --- src/reHDD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reHDD.cpp b/src/reHDD.cpp index fbc8d96..e1f91bc 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -237,7 +237,7 @@ void reHDD::filterNewDrives(vector * pvecOldDrives, vector * pvecN itOld->bIsOffline = true; //set offline befor seachring in the new list for (itNew = pvecNewDrives->begin(); itNew != pvecNewDrives->end();) { - if(itOld->getSerial() == itNew->getSerial()) + if((itOld->getSerial() == itNew->getSerial()) || (itOld->getPath() == itNew->getPath())) { itOld->bIsOffline = false; //drive is still attached #ifdef LOG_LEVEL_HIGH From 4d967d8a8e570ff134e860523a51ddabfa677dad Mon Sep 17 00:00:00 2001 From: Hendrik Schutter Date: Tue, 15 Sep 2020 13:12:31 +0200 Subject: [PATCH 38/41] added build notes --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6b267fe..61e4447 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ * Check S.M.A.R.T. values and make an 'passed' or 'not passed' decision * If passed, wipe the data securely -## Debian Install Notes +## Debian Build Notes + +* apt-get install ncurses-dev git make g++ +* clone repo +* make release -* apt-get install ncurses-dev -* apt-get install hwinfo -* wget http://ftp.de.debian.org/debian/pool/main/s/smartmontools/smartmontools_7.1-1_amd64.deb -* dpkg --install smartmontools_7.1-1_amd64.deb From 92bd9a70a377946a7dfaa3beabd152e1309673a9 Mon Sep 17 00:00:00 2001 From: Hendrik Schutter Date: Tue, 15 Sep 2020 13:28:02 +0200 Subject: [PATCH 39/41] added notes for standalone --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 61e4447..ee81d9d 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,10 @@ ## Useful for: * checking new drives for the first time * checking used drives for their next live -* deleting a drive securely +* deleting a drive securely via overwriting -## planned Features: - -* search for new attached Hard Drives via USB -* display Hard Drive Manufacturer, Model, Rotation Rate and Capacity -* Check S.M.A.R.T. values and make an 'passed' or 'not passed' decision -* If passed, wipe the data securely +## Screenshot +![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") ## Debian Build Notes @@ -18,5 +14,65 @@ * clone repo * make release +## Create Standalone with Debian +Instructions how to create a standalone machine that boots directly to reHDD. This is aimed for production use, like several drives a day shredding. + +### Software requirements + +* apt-get install hwinfo +* wget http://ftp.de.debian.org/debian/pool/main/s/smartmontools/smartmontools_7.1-1_amd64.deb +* dpkg --install smartmontools_7.1-1_amd64.deb + +### Start reHDD after boot without login (as a tty shell) + +nano /etc/systemd/system/reHDD.service +``` +[Unit] +Description=Custom user interface on tty1 +Conflicts=getty@tty1.service +Before=getty.target + +[Service] +WorkingDirectory=/root/reHDD +ExecStart=/root/reHDD/reHDD +StandardInput=tty +StandardOutput=tty +Restart=always +RestartSec=1 +UtmpIdentifier=tty1 +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +SendSIGHUP=yes + +[Install] +WantedBy=multi-user.target +``` + +nano /etc/systemd/system/reHDDSettings.service +``` +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/bash /root/reHDDSettings.sh + +[Install] +WantedBy=multi-user.target +``` + +nano /root/reHDDSettings.sh +``` +#!/bin/bash +dmesg -n 1 #disable overlay if a drive is attached/detached +rm -f /root/reHDD/reHDD.log +``` +Make sure the binary reHDD is in /root/reHDD/ +Add your system drive in /root/reHDD/ignoreDrives.conf like: +``` /dev/sdX:e102f49d-5ed5-462b-94c5-ef66a4345671``` +Get your UUID via blkid /dev/sdX + +systemctl enable reHDD.service +systemctl enable reHDDSettings.service From 95828afcc2e417b9cb64a4add98ae9c3c7628e84 Mon Sep 17 00:00:00 2001 From: localhorst Date: Tue, 15 Sep 2020 13:39:45 +0200 Subject: [PATCH 40/41] first beta release --- doc/screenshot.png | Bin 0 -> 54981 bytes ignoreDrives.conf | 4 +--- include/reHDD.h | 6 ++++-- src/tui.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 doc/screenshot.png diff --git a/doc/screenshot.png b/doc/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..760671298e859326bc876d7bf5f3b7b70c550929 GIT binary patch literal 54981 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 Date: Tue, 15 Sep 2020 13:43:51 +0200 Subject: [PATCH 41/41] added screenshot --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ee81d9d..ddf5bc8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ * deleting a drive securely via overwriting ## Screenshot -![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") +![alt text](https://git.mosad.xyz/localhorst/reHDD/raw/commit/95828afcc2e417b9cb64a4add98ae9c3c7628e84/doc/screenshot.png "Screenshot") ## Debian Build Notes @@ -74,5 +74,6 @@ Add your system drive in /root/reHDD/ignoreDrives.conf like: Get your UUID via blkid /dev/sdX systemctl enable reHDD.service + systemctl enable reHDDSettings.service