diff --git a/Makefile b/Makefile index b642fb4..ca98d52 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ CXX := g++ + SRCDIR := src +BINDIR := bin SRCEXT := cpp SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) @@ -8,7 +10,7 @@ INC := -I lib -I include build: mkdir -p bin - $(CXX) $(CXXFLAGS) $(INC) $(SOURCES) -o bin/hexdump + $(CXX) $(CXXFLAGS) $(INC) $(SOURCES) -o $(BINDIR)/hexdump .PHONY: clean clean: diff --git a/include/hexdump_errno.h b/include/hexdump_errno.h index a332f7d..e3c9819 100644 --- a/include/hexdump_errno.h +++ b/include/hexdump_errno.h @@ -5,5 +5,6 @@ #define HEX_EIO 2 // I/O error #define HEX_EFBIG 3 // File too big #define HEX_EFEMPTY 4 // File is empty +#define HEX_ENOENT 5 // File does not exist #endif \ No newline at end of file diff --git a/src/hexdump.cpp b/src/hexdump.cpp index 56dbcca..0f43c78 100644 --- a/src/hexdump.cpp +++ b/src/hexdump.cpp @@ -1,40 +1,66 @@ #include +#include #include #include -#include #include #include +#include #include #include +// Check windows +#ifdef _WIN32 +#include +#endif + using namespace cxxopts; using std::string; +namespace fs = std::filesystem; const string hexdump_version = "v1.1.0"; -const unsigned int max_file_size = 0xFFFFFFFF; +const size_t max_file_size = 0xFFFFFFFF; +const size_t max_log_count = 128; -const string error_header = "\x1b[1;38;2;255;0;0mError: \x1b[0m"; +string binary_name; + +string error_header = "\x1b[1;38;2;255;0;0mError: \x1b[0m"; string ansi_reset = "\x1b[0m"; string offset_color = "\x1b[38;2;0;144;255m"; string ascii_color = "\x1b[38;2;0;144;48m"; -bool output_color = true; +enum log_level { + LOG_INFO, + LOG_WARN, + LOG_ERROR +}; + +const string enum_vals[] = { + "INFO", + "WARN", + "ERROR" +}; ParseResult initialize_options(int argc, char** argv); string int_to_hex(int value, int width); void outputHexLine(std::ostream& output, std::vector buffer, size_t offset, size_t size, int ascii); +void log(string message, log_level level = LOG_INFO, bool output = false); int main(int argc, char** argv) { ParseResult result = initialize_options(argc, argv); const string filename = result["file"].as(); + if (!fs::exists(filename)) { + std::cerr << error_header << "File '" + filename + "' does not exist"; + return HEX_ENOENT; + } + std::ifstream input_stream(filename, std::ios::binary | std::ios::in); if (!input_stream.is_open()) { - std::cerr << error_header << "Could not open file '" << filename << "'" << std::endl; + std::cerr << error_header << "Failed to open file '" << filename << "'" << std::endl; return HEX_EIO; } @@ -43,8 +69,8 @@ int main(int argc, char** argv) { input_stream.seekg(0, input_stream.beg); if (file_size > max_file_size) { - std::cerr << error_header << "File is too big" << std::endl; - return HEX_EFBIG; + std::cerr << error_header << "File size is too large" << std::endl; + return HEX_EFBIG; } else if (file_size == 0) { std::cerr << error_header << "File is empty" << std::endl; return HEX_EFEMPTY; @@ -56,14 +82,8 @@ int main(int argc, char** argv) { buffer.push_back(byte); } - if (!output_color) { - ansi_reset = ""; - offset_color = ""; - ascii_color = ""; - } - std::stringstream output = std::stringstream(); - + output << offset_color << " Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F "; if (result.count("ascii")) { @@ -81,7 +101,6 @@ int main(int argc, char** argv) { output_stream.open(output_filename, std::ios::out); output_stream << output.str() << std::endl; output_stream.close(); - std::cout << "Wrote output to " << output_filename << std::endl; } else { std::cout << output.str() << std::endl; @@ -91,7 +110,7 @@ int main(int argc, char** argv) { } ParseResult initialize_options(int argc, char** argv) { - string binary_name = std::filesystem::path(argv[0]).filename().string(); + binary_name = fs::path(argv[0]).filename().string(); Options options(binary_name, "A simple hexdump utility\n"); @@ -122,26 +141,39 @@ ParseResult initialize_options(int argc, char** argv) { std::cout << "Hexdump " << hexdump_version << std::endl; exit(EXIT_SUCCESS); } - - if (!result.count("file")) { - std::cout << error_header << "No file specified\n" - << "\n" - << "Usage: " << binary_name << " [options...] \n" - << "Try `" << binary_name << " --help` for more information." << std::endl; - exit(HEX_EMISARG); - } - if ((result.count("output-color") && - result["output-color"].as() == "false") || - result.count("output") - ) { - output_color = false; + result["output-color"].as() == "false") || + (result.count("output") && !(result.count("output-color") && + result["output-color"].as() == "true"))) { + ansi_reset = ""; + offset_color = ""; + ascii_color = ""; + error_header = "Error: "; + } +#ifdef _WIN32 + else { + DWORD dwMode = 0; + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleMode(hConsole, &dwMode); + DWORD dwNewMode = dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(hConsole, dwNewMode); + GetConsoleMode(hConsole, &dwMode); + + if (dwMode == dwNewMode) { + log("Enabled VT100 escape codes", LOG_INFO) + } else { + log("Failed to enable VT100 escape codes", LOG_WARN); + exit(128); + } } +#endif - if ((result.count("output-color") && - result["output-color"].as() == "true") - ) { - output_color = true; + if (!result.count("file")) { + std::cerr << error_header << "Missing required argument: file\nUsage: " + << binary_name << " [options...] \nTry `" << binary_name + << " --help` for more information." << std::endl; + + exit(HEX_EMISARG); } return result; @@ -182,3 +214,57 @@ void outputHexLine(std::ostream& output, std::vector buffer, size } output << ansi_reset << "\n"; } + +string timestamp() { + time_t timer; + char buffer[26]; + struct tm* tm_info; + + timer = time(NULL); + tm_info = localtime(&timer); + + strftime(buffer, 26, "%Y-%m-%d, %H:%M:%S", tm_info); + + return string(buffer); +} + +void log(string message, log_level level, bool output) { + string home_directory; +#ifdef _WIN32 + home_directory = std::getenv("HOMEDRIVE") + std::getenv("HOMEPATH"); +#else + home_directory = std::getenv("HOME"); +#endif + fs::path log_path = fs::path(home_directory) + .append("." + binary_name); + + if (!fs::exists(log_path)) { + fs::create_directory(log_path); + } + + log_path = log_path.append("logs"); + + if (!fs::exists(log_path)) { + fs::create_directory(log_path); + } + + if (!fs::is_directory(log_path)) { + // TODO: Implement failsafe if log_path cannot be created + } + + fs::path log = log_path.append(binary_name + ".log"); + + string log_message = "[" + timestamp() + "] [" + enum_vals[level] + "]: " + message; + + std::ofstream log_stream = std::ofstream(log, std::ios::out | std::ios::app); + log_stream << log_message << std::endl; + log_stream.close(); + + if (output) { + if (level == LOG_ERROR) { + std::cerr << error_header << message << std::endl; + } else { + std::cout << message << std::endl; + } + } +} \ No newline at end of file