diff --git a/src/app.cpp b/src/app.cpp index d7fdc92..388226e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -29,6 +29,9 @@ void App::app_logic(void) searchDrives(&listDrives); //search for new drives and store them in list filterIgnoredDrives(&listDrives); //filter out ignored drives + + addSMARTData(&listDrives); + printDrives(&listDrives); //print currently attached drives } @@ -54,10 +57,7 @@ void App::searchDrives(list *listDrives) { if (string(cLine).find("/dev/sd") != string::npos) { - //struct drive tmpDrive; - Drive* tmpDrive = new Drive(string(cLine).substr (2,8)); - listDrives->push_back(*tmpDrive); } } @@ -162,8 +162,8 @@ void App::printDrives(list *listDrives) for (it = listDrives->begin(); it != listDrives->end(); ++it) { cout << "Path: " << it->getPath() << endl; - cout << "Model: " << it->getModel() << endl; - cout << "Manufacturer: " << it->getManufacturer() << 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; @@ -173,3 +173,11 @@ void App::printDrives(list *listDrives) } cout << "---------------------------------" << endl; } + +void App::addSMARTData(list *listDrives) { + list ::iterator it; + for (it = listDrives->begin(); it != listDrives->end(); ++it) + { + SMART::readSMARTData(*it); + } +} \ No newline at end of file diff --git a/src/app.h b/src/app.h index c401972..24a7cb5 100644 --- a/src/app.h +++ b/src/app.h @@ -10,6 +10,7 @@ #include "refurbishingHddTool.h" #include "drive.h" +#include "smart.h" class App { protected: @@ -26,6 +27,7 @@ private: void searchDrives(list *listDrives); void printDrives(list *listDrives); void filterIgnoredDrives(list *listDrives); + void addSMARTData(list *listDrives); }; diff --git a/src/drive.cpp b/src/drive.cpp index 5339491..76d45cb 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -13,14 +13,14 @@ string Drive::getPath(void) return sPath; } -string Drive::getManufacturer(void) +string Drive::getModelFamily(void) { - return sManufacturer; + return sModelFamily; } -string Drive::getModel(void) +string Drive::getModelName(void) { - return sModel; + return sModelName; } string Drive::getSerial(void) @@ -47,3 +47,19 @@ uint32_t Drive::getSpinUpCount(void) return u32SpinUpCount; } +void Drive::setDriveSMARTData( string modelFamily, + string modelName, + string serial, + string capacity, + uint32_t errorCount, + uint32_t powerOnHours, + uint32_t spinUpCount) +{ + sModelFamily = modelFamily; + sModelName = modelName; + sSerial = serial; + sCapacity = capacity; + u32ErrorCount = errorCount; + u32PowerOnHours = powerOnHours; + u32SpinUpCount = spinUpCount; +} \ No newline at end of file diff --git a/src/drive.h b/src/drive.h index c0bfd06..8bc8456 100644 --- a/src/drive.h +++ b/src/drive.h @@ -19,16 +19,16 @@ public: } string getPath(void); - string getManufacturer(void); - string getModel(void); + string getModelFamily(void); + string getModelName(void); string getSerial(void); string getCapacity(void); uint32_t getErrorCount(void); uint32_t getPowerOnHours(void); uint32_t getSpinUpCount(void); - void setDriveSMARTData( string manufacturer, - string model, + void setDriveSMARTData( string modelFamily, + string modelName, string serial, string capacity, uint32_t errorCount, @@ -37,8 +37,8 @@ public: private: string sPath; - string sManufacturer; - string sModel; + string sModelFamily; + string sModelName; string sSerial; string sCapacity; uint32_t u32ErrorCount; diff --git a/src/makefile b/src/makefile index 8e669ea..2f56294 100644 --- a/src/makefile +++ b/src/makefile @@ -1,5 +1,5 @@ -refurbishingHddTool: main.o app.o drive.o - g++ -Wall -o refurbishingHddTool main.o app.o drive.o +refurbishingHddTool: main.o app.o drive.o smart.o + g++ -Wall -o refurbishingHddTool main.o app.o drive.o smart.o main.o: main.cpp g++ -c main.cpp @@ -9,6 +9,9 @@ app.o: app.cpp drive.o: drive.cpp g++ -c drive.cpp + +smart.o: smart.cpp + g++ -c smart.cpp clean : - rm refurbishingHddTool main.o app.o drive.o + rm refurbishingHddTool main.o app.o drive.o smart.o diff --git a/src/out.txt b/src/out.txt new file mode 100644 index 0000000..fdeaecf --- /dev/null +++ b/src/out.txt @@ -0,0 +1,803 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 1 + ], + "svn_revision": "5022", + "platform_info": "x86_64-linux-5.6.4-1-default", + "build_info": "(SUSE RPM)", + "argv": [ + "smartctl", + "--json", + "-a", + "/dev/sdd" + ], + "exit_status": 0 + }, + "device": { + "name": "/dev/sdd", + "info_name": "/dev/sdd [SAT]", + "type": "sat", + "protocol": "ATA" + }, + "model_family": "Western Digital Caviar Green", + "model_name": "WDC WD15EADS-22P8B0", + "serial_number": "WD-WMAVU2708194", + "wwn": { + "naa": 5, + "oui": 5358, + "id": 28633198579 + }, + "firmware_version": "01.00A01", + "user_capacity": { + "blocks": 2930277168, + "bytes": 1500301910016 + }, + "logical_block_size": 512, + "physical_block_size": 512, + "in_smartctl_database": true, + "ata_version": { + "string": "ATA8-ACS (minor revision not indicated)", + "major_value": 510, + "minor_value": 0 + }, + "sata_version": { + "string": "SATA 2.6", + "value": 30 + }, + "interface_speed": { + "max": { + "sata_value": 6, + "string": "3.0 Gb/s", + "units_per_second": 30, + "bits_per_unit": 100000000 + } + }, + "local_time": { + "time_t": 1588373001, + "asctime": "Sat May 2 00:43:21 2020 CEST" + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 130, + "string": "was completed without error", + "passed": true + }, + "completion_seconds": 31200 + }, + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + }, + "polling_minutes": { + "short": 2, + "extended": 356, + "conveyance": 5 + } + }, + "capabilities": { + "values": [ + 123, + 3 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": true, + "self_tests_supported": true, + "conveyance_self_test_supported": true, + "selective_self_test_supported": true, + "attribute_autosave_enabled": true, + "error_logging_supported": true, + "gp_logging_supported": true + } + }, + "ata_sct_capabilities": { + "value": 12343, + "error_recovery_control_supported": false, + "feature_control_supported": true, + "data_table_supported": true + }, + "ata_smart_attributes": { + "revision": 16, + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 51, + "when_failed": "", + "flags": { + "value": 47, + "string": "POSR-K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 3, + "name": "Spin_Up_Time", + "value": 182, + "worst": 178, + "thresh": 21, + "when_failed": "", + "flags": { + "value": 39, + "string": "POS--K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 5858, + "string": "5858" + } + }, + { + "id": 4, + "name": "Start_Stop_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 730, + "string": "730" + } + }, + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 200, + "worst": 200, + "thresh": 140, + "when_failed": "", + "flags": { + "value": 51, + "string": "PO--CK ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 7, + "name": "Seek_Error_Rate", + "value": 100, + "worst": 253, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 46, + "string": "-OSR-K ", + "prefailure": false, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 9, + "name": "Power_On_Hours", + "value": 99, + "worst": 99, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 1227, + "string": "1227" + } + }, + { + "id": 10, + "name": "Spin_Retry_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 11, + "name": "Calibration_Retry_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 711, + "string": "711" + } + }, + { + "id": 192, + "name": "Power-Off_Retract_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 350, + "string": "350" + } + }, + { + "id": 193, + "name": "Load_Cycle_Count", + "value": 193, + "worst": 193, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 21650, + "string": "21650" + } + }, + { + "id": 194, + "name": "Temperature_Celsius", + "value": 114, + "worst": 86, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 34, + "string": "-O---K ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 36, + "string": "36" + } + }, + { + "id": 196, + "name": "Reallocated_Event_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 197, + "name": "Current_Pending_Sector", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 198, + "name": "Offline_Uncorrectable", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 48, + "string": "----CK ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "UDMA_CRC_Error_Count", + "value": 200, + "worst": 199, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 332, + "string": "332" + } + }, + { + "id": 200, + "name": "Multi_Zone_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 8, + "string": "---R-- ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + } + ] + }, + "power_on_time": { + "hours": 1227 + }, + "power_cycle_count": 711, + "temperature": { + "current": 36 + }, + "ata_smart_error_log": { + "summary": { + "revision": 1, + "count": 0 + } + }, + "ata_smart_self_test_log": { + "standard": { + "revision": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1191 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1170 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1155 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1141 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1127 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1112 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1100 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1087 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1074 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1060 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1047 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1034 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1021 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1005 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1003 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 992 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 987 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 975 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 234 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 230 + } + ], + "count": 20, + "error_count_total": 0, + "error_count_outdated": 0 + } + }, + "ata_smart_selective_self_test_log": { + "revision": 1, + "table": [ + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + } + ], + "flags": { + "value": 0, + "remainder_scan_enabled": false + }, + "power_up_scan_resume_minutes": 0 + } +} diff --git a/src/refurbishingHddTool b/src/refurbishingHddTool index 6bb29a1..953e709 100755 Binary files a/src/refurbishingHddTool and b/src/refurbishingHddTool differ diff --git a/src/smart.cpp b/src/smart.cpp index e69de29..7240f0c 100644 --- a/src/smart.cpp +++ b/src/smart.cpp @@ -0,0 +1,134 @@ +/** + * @file smart.cpp + * @brief read S.M.A.R.T values + * @author hendrik schutter + * @date 01.05.2020 + */ + +#include "smart.h" + +string SMART::modelFamily; +string SMART::modelName; +string SMART::serial; +string SMART::capacity; +uint32_t SMART::errorCount = 0U; +uint32_t SMART::powerOnHours = 0U; +uint32_t SMART::spinUpCount = 0U; + +void SMART::readSMARTData(Drive drive) +{ + size_t len = 0; //lenght of found line + char* cLine = NULL; //found line + + string sCMD = ("./smartctl --json -a "); + sCMD.append(drive.getPath()); + const char* cpComand = sCMD.c_str(); + + FILE* outputfileSmart = popen(cpComand, "r"); + + while ((getline(&cLine, &len, outputfileSmart)) != -1) { + string sLine = string(cLine); + + SMART::parseModelFamily(sLine); + SMART::parseModelName(sLine); + SMART::parseSerial(sLine); + SMART::parseCapacity(sLine); + SMART::parseErrorCount(sLine); + SMART::parsePowerOnHours(sLine); + SMART::parseSpinUpCount(sLine); + } + fclose(outputfileSmart); + //drive.setDriveSMARTData(modelFamily, modelName, serial, capacity, errorCount, powerOnHours, spinUpCount); +} + +void SMART::parseModelFamily(string sLine) +{ + string search("\"model_family\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + // modelFamily = sLine; + cout << "ModelFamily |" << sLine << "|" << endl; + } +} + +void SMART::parseModelName(string sLine) +{ + string search("\"model_name\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + // modelName = sLine; + cout << "ModelName |" << sLine << "|" << endl; + } +} + +void SMART::parseSerial(string sLine) +{ + string search("\"serial_number\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 3); + sLine.erase(sLine.length()-3, 3); + // serial = sLine; + cout << "Serial |" << sLine << "|" << endl; + } +} + +void SMART::parseCapacity(string sLine) +{ + string search("\"bytes\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-1, 1); + // capacity = sLine; + cout << "Capacity |" << sLine << "|" << endl; + } +} + +void SMART::parseErrorCount(string sLine) +{ + string search("\"error_count_total\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + //cout << sLine; + sLine.erase(0, sLine.find(": ")+2); + sLine.erase(sLine.length()-2, 2); + errorCount = stoi(sLine); + cout << "ErrorCount |" << sLine << "|" << endl; + } +} + +void SMART::parsePowerOnHours(string sLine) +{ +string search("\"hours\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-1, 1); + // powerOnHours = stoi(sLine); + cout << "PowerOnHours |" << sLine << "|" << endl; + } +} + +void SMART::parseSpinUpCount(string sLine) +{ +string search("\"power_cycle_count\": "); + size_t found = sLine.find(search); + if (found!=string::npos) { + // cout << sLine; + sLine.erase(0, sLine.find(": ") + 2); + sLine.erase(sLine.length()-2, 2); + //spinUpCount = stoi(sLine); + cout << "SpinUpCount |" << sLine << "|" << endl; + } +} + diff --git a/src/smart.h b/src/smart.h new file mode 100644 index 0000000..c152b4d --- /dev/null +++ b/src/smart.h @@ -0,0 +1,47 @@ +/** + * @file smart.h + * @brief read S.M.A.R.T values + * @author hendrik schutter + * @date 01.05.2020 + */ + +#ifndef SMART_H_ +#define SMART_H_ + +#include "refurbishingHddTool.h" +#include "drive.h" + + +class SMART { +protected: + + +public: + + static void readSMARTData(Drive drive); + + +private: + SMART(void); + + static void parseModelFamily(string sLine); + static void parseModelName(string sLine); + static void parseSerial(string sLine); + static void parseCapacity(string sLine); + static void parseErrorCount(string sLine); + static void parsePowerOnHours(string sLine); + static void parseSpinUpCount(string sLine); + + static string modelFamily; + static string modelName; + static string serial; + static string capacity; + static uint32_t errorCount; + static uint32_t powerOnHours; + static uint32_t spinUpCount; + +}; + + + +#endif // SMART_H_ \ No newline at end of file diff --git a/vcCodium.code-workspace b/vcCodium.code-workspace index a921fa8..772c2aa 100644 --- a/vcCodium.code-workspace +++ b/vcCodium.code-workspace @@ -6,7 +6,11 @@ ], "settings": { "files.associations": { - "iostream": "cpp" + "iostream": "cpp", + "*.tcc": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "string_view": "cpp" } } } \ No newline at end of file