From e3c95964b3d6bb06d39ddc07cab0f5ec3187eaa5 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 10 Oct 2023 14:01:09 -0700 Subject: [PATCH] Finish phonebook command and add to install targets. --- Arena.cc | 10 ++-- Arena.h | 16 +----- CMakeLists.txt | 10 ++-- Commander.cpp | 78 +++++++++++++++++++++++++++ Commander.h | 59 ++++++++++++++++++++ Dictionary.cc | 18 ++++++- Dictionary.h | 9 +++- dictionaryTest.cc | 2 +- phonebook.cpp | 134 +++++++++++++++++++++++++++++++++++++++++----- tlvTest.cc | 2 +- 10 files changed, 297 insertions(+), 41 deletions(-) create mode 100644 Commander.cpp create mode 100644 Commander.h diff --git a/Arena.cc b/Arena.cc index 257a56b..ccdb3c7 100644 --- a/Arena.cc +++ b/Arena.cc @@ -123,25 +123,27 @@ Arena::Open(const char *path) int -Arena::Create(const char *path, size_t fileSize, mode_t mode) +Arena::Create(const char *path, size_t fileSize) { + FILE *fHandle = nullptr; int newFileDes = 0; if (this->size > 0) { this->Destroy(); } - newFileDes = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); - if (newFileDes == -1) { + + fHandle = fopen(path, "w"); + if (fHandle == nullptr) { return -1; } + newFileDes = fileno(fHandle); if (ftruncate(newFileDes, fileSize) == -1) { return -1; } close(newFileDes); - return this->Open(path); } #elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) diff --git a/Arena.h b/Arena.h index 1eeb520..f9cf098 100644 --- a/Arena.h +++ b/Arena.h @@ -35,15 +35,6 @@ namespace klib { -/// DefaultFileMode is a sane set of default permissions that can be used for a -/// new Arena. -#if defined(__linux__) -static constexpr mode_t DefaultFileMode = 0644; -#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) -static constexpr DWORD DefaultFileMode = - (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE); -#endif - /// \enum ArenaType /// @@ -126,10 +117,9 @@ public: /// /// \param path The path to the file that should be created. /// \param fileSize The size of the file to create. - /// \param mode The permissions to load. /// \return Returns 0 on success and -1 on error. #if defined(__linux__) - int Create(const char *path, size_t fileSize, mode_t mode); + int Create(const char *path, size_t fileSize); #elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) int Create(const char *path, size_t fileSize, DWORD mode); @@ -144,11 +134,7 @@ public: /// /// \param path The path to the file to be loaded. /// \return Returns 0 on success and -1 on error. -#if defined(__linux__) int Open(const char *path); -#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) - int Open(const char *path); -#endif /// NewCursor returns a pointer to the start of the memory in the arena. /// diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b2be50..2bcc895 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,12 @@ set(SOURCE_FILES Dictionary.cc Exceptions.cpp Test.cc - TLV.cc) + TLV.cc + Commander.cpp + Commander.h) add_library(klib STATIC ${SOURCE_FILES} ${HEADER_FILES}) + add_executable(phonebook phonebook.cpp) target_link_libraries(phonebook klib) @@ -67,8 +70,9 @@ add_custom_target(cloc WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) configure_file(klib.pc.in klib.pc @ONLY) -install(TARGETS klib LIBRARY DESTINATION ${PREFIX}/lib) -install(FILES ${HEADER_FILES} DESTINATION include/{klib}) +install(TARGETS klib LIBRARY DESTINATION lib) +install(TARGETS phonebook RUNTIME DESTINATION bin) +install(FILES ${HEADER_FILES} DESTINATION include/klib) install(FILES klibConfig.cmake DESTINATION share/klib/cmake) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/klib.pc DESTINATION lib/pkgconfig) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/klib) diff --git a/Commander.cpp b/Commander.cpp new file mode 100644 index 0000000..c4f9a9b --- /dev/null +++ b/Commander.cpp @@ -0,0 +1,78 @@ +/// +/// \file Commander.cpp +/// \author kyle +/// \created 10/10/23 +/// \brief +/// \section COPYRIGHT +/// \section COPYRIGHT +/// Copyright 2023 K. Isom +/// +/// Permission to use, copy, modify, and/or distribute this software for +/// any purpose with or without fee is hereby granted, provided that the +/// above copyright notice and this permission notice appear in all copies. +/// +/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR +/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +/// SOFTWARE. +/// +/// @\section DESCRIPTION + +#include +#include "Commander.h" + +namespace klib { + + +Subcommand::Status +Subcommand::Run(int argc, char **argv) +{ + if (argc < this->args) { + std::cerr << "[!] " << this->command << " expects "; + std::cerr << this->args << " args, but was given "; + std::cerr << argc << " args.\n"; + return Subcommand::Status::NotEnoughArgs; + } + if (this->fn(argc, argv)) { + return Subcommand::Status::OK; + } + + return Subcommand::Status::Failed; +} + +Commander::Commander() + : cmap() +{ + this->cmap.clear(); +} + +bool +Commander::Register(Subcommand scmd) +{ + if (this->cmap.count(scmd.Name()) > 0) { + return false; + } + + auto *pScmd = new Subcommand(scmd); + this->cmap[scmd.Name()] = pScmd; + return true; +} + + +Subcommand::Status +Commander::Run(std::string command, int argc, char **argv) +{ + if (this->cmap.count(command) != 1) { + return Subcommand::Status::CommandNotRegistered; + } + + auto scmd = this->cmap[command]; + return scmd->Run(argc, argv); +} + + +} // klib \ No newline at end of file diff --git a/Commander.h b/Commander.h new file mode 100644 index 0000000..af873c2 --- /dev/null +++ b/Commander.h @@ -0,0 +1,59 @@ +/// +/// \file Commander.h +/// \author kyle +/// \created 2023-10-10 +/// \brief Subprogram tooling. + +#include +#include +#include +#include + + +#ifndef KLIB_COMMANDER_H +#define KLIB_COMMANDER_H + +namespace klib { + + +typedef std::function CommanderFunc; + + +class Subcommand { +public: + enum class Status : uint8_t { + OK = 0, + NotEnoughArgs = 1, + Failed = 2, + CommandNotRegistered = 3, + }; + + Subcommand(std::string name, int argc, CommanderFunc func) + : fn(func), args(argc), command(name) + {} + + std::string Name() { return this->command; } + Status Run(int argc, char **argv); +private: + CommanderFunc fn; + int args; + std::string command; +}; + +/// Commander does this. +/// +/// Longer description... +class Commander { +public: + Commander(); + + bool Register(Subcommand scmd); + Subcommand::Status Run(std::string command, int argc, char **argv); +private: + std::map cmap; +}; + +} // klib + + +#endif //KLIB_COMMANDER_H diff --git a/Dictionary.cc b/Dictionary.cc index 2d2e5dc..9e38127 100644 --- a/Dictionary.cc +++ b/Dictionary.cc @@ -64,7 +64,7 @@ Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen) } - +/// seek searches the Dictionary for the key. uint8_t * Dictionary::seek(const char *key, uint8_t klen) { @@ -94,6 +94,21 @@ Dictionary::Contains(const char *key, uint8_t klen) } +bool +Dictionary::Delete(const char *key, uint8_t klen) +{ + auto *cursor = this->seek(key, klen); + + if (cursor == nullptr) { + return false; + } + + TLV::DeleteRecord(this->arena, cursor); + TLV::DeleteRecord(this->arena, cursor); + return true; +} + + bool Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen) { @@ -123,7 +138,6 @@ operator<<(std::ostream &os, const Dictionary &dictionary) TLV::Record rec; TLV::ReadFromMemory(rec, cursor); - os << "Dictionary KV pairs" << std::endl; if (rec.Tag == TLV::TAG_EMPTY) { os << "\t(NONE)" << std::endl; return os; diff --git a/Dictionary.h b/Dictionary.h index 412f43c..73cd986 100644 --- a/Dictionary.h +++ b/Dictionary.h @@ -91,6 +91,13 @@ public: /// \return True if the key is in the Dictionary, otherwise false. bool Contains(const char *key, uint8_t klen); + /// Delete removes the key from the Dictionary. + /// + /// \param key The key to look up. + /// \param klen The length of the key. + /// \return True if the key was removed, otherwise false. + bool Delete(const char *key, uint8_t klen); + /// DumpToFile is a wrapper aorund a call to Arena::Write on the /// underlying Arena. @@ -99,7 +106,7 @@ public: /// \return 0 on success, -1 on failure. int DumpToFile(const char *path); - /// operator<< writes the key pairs to the output stream. + /// operator<< writes the key pairs phonto the output stream. /// /// \param os The output stream to write to. /// \param dictionary The dictionary to write out. diff --git a/dictionaryTest.cc b/dictionaryTest.cc index 6843317..074bff9 100644 --- a/dictionaryTest.cc +++ b/dictionaryTest.cc @@ -45,7 +45,7 @@ main(int argc, const char *argv[]) std::cout << "TESTPROG: " << argv[0] << std::endl; #if defined(__linux__) - if (arena.Create(ARENA_FILE, ARENA_SIZE, 0644) == -1) { + if (arena.Create(ARENA_FILE, ARENA_SIZE) == -1) { abort(); } #else diff --git a/phonebook.cpp b/phonebook.cpp index 4adb94e..364f3be 100644 --- a/phonebook.cpp +++ b/phonebook.cpp @@ -7,10 +7,96 @@ using namespace std; #include "Arena.h" +#include "Commander.h" #include "Dictionary.h" using namespace klib; static const char *defaultPhonebook = "pb.dat"; +static char *pbFile = (char *)defaultPhonebook; +static Arena arena; +static Dictionary pb(arena); + + +static bool +listFiles(int argc, char **argv) +{ + (void)argc; (void)argv; + cout << "[+] keys in '" << pbFile << "':\n"; + cout << pb; + return true; +} + +static bool +newPhonebook(int argc, char **argv) +{ + (void)argc; + + auto size = std::stoul(string(argv[0])); + cout << "[+] create new " << size << "B phonebook '" << pbFile << "'\n"; + + return arena.Create(pbFile, size) == 0; +} + +static bool +delKey(int argc, char **argv) +{ + (void)argc; + string key = argv[0]; + + cout << "[+] deleting key '" << key << "'\n"; + return pb.Delete(key.c_str(), key.size()); +} + +static bool +hasKey(int argc, char **argv) +{ + (void)argc; + string key = argv[0]; + + cout << "[+] looking up '" << key << "': "; + if (pb.Contains(key.c_str(), key.size())) { + cout << "found\n"; + return true; + } + + cout << "not found\n"; + return true; +} + +static bool +getKey(int argc, char **argv) +{ + (void)argc; + TLV::Record rec; + auto key = string(argv[0]); + + cout << "[+] key '" << key << "' "; + if (!pb.Lookup(key.c_str(), key.size(), rec)) { + cout << "not found\n"; + return false; + } + + cout << "-> " << rec.Val << "\n"; + return true; +} + + +static bool +putKey(int argc, char **argv) +{ + (void)argc; + auto key = string(argv[0]); + auto val = string(argv[1]); + + cout << "[+] setting '" << key << "' -> '" << val << "': "; + if (pb.Set(key.c_str(), key.size(), val.c_str(), val.size()) != 0) { + cout << "failed\n"; + return false; + } + + cout << "set\n"; + return true; +} static void usage(ostream &os, int exc) @@ -19,9 +105,10 @@ usage(ostream &os, int exc) os << "\nThe default filename is pb.dat.\n\n"; os << "Usage:\n"; os << "\tphonebook [-f file] list\n"; + os << "\tphonebook [-f file] new size\n"; os << "\tphonebook [-f file] del key\n"; os << "\tphonebook [-f file] has key\n"; - os << "\tphonebook [-f file] get key value\n"; + os << "\tphonebook [-f file] get key\n"; os << "\tphonebook [-f file] put key value\n"; os << "\n"; @@ -31,12 +118,9 @@ usage(ostream &os, int exc) int main(int argc, char *argv[]) { - Arena arena; - Dictionary pb(arena); - char *pbFile = (char *)defaultPhonebook; int optind = 1; - for (optind; optind < argc; optind++) { + for (optind = 1; optind < argc; optind++) { auto arg = string(argv[optind]); if (arg[0] != '-') break; if (arg == "-h") usage(cout, 0); @@ -53,18 +137,40 @@ main(int argc, char *argv[]) usage(cout, 0); } - cout << "[+] loading phonebook from " << pbFile << "\n"; - if (arena.Open(pbFile) != 0) { - cerr << "Failed to open " << pbFile << "\n"; - exit(1); - } - auto command = string(argv[optind++]); + Commander commander; - if (command == "list") { - cout << pb << "\n"; - } else if (command == "del") { + commander.Register(Subcommand("list", 0, listFiles)); + commander.Register(Subcommand("new", 1, newPhonebook)); + commander.Register(Subcommand("del", 1, delKey)); + commander.Register(Subcommand("has", 1, hasKey)); + commander.Register(Subcommand("get", 1, getKey)); + commander.Register(Subcommand("put", 2, putKey)); + if (command != "new") { + cout << "[+] loading phonebook from " << pbFile << "\n"; + if (arena.Open(pbFile) != 0) { + cerr << "Failed to open " << pbFile << "\n"; + exit(1); + } } + auto result = commander.Run(command, argc-optind, argv+optind); + switch (result) { + case Subcommand::Status::OK: + std::cout << "[+] OK\n"; + break; + case Subcommand::Status::NotEnoughArgs: + usage(cerr, 1); + break; + case Subcommand::Status::Failed: + cerr << "[!] '"<< command << "' failed\n"; + break; + case Subcommand::Status::CommandNotRegistered: + cerr << "[!] '" << command << "' not registered.\n"; + usage(cerr, 1); + break; + default: + abort(); + } } \ No newline at end of file diff --git a/tlvTest.cc b/tlvTest.cc index c53661e..eab0116 100644 --- a/tlvTest.cc +++ b/tlvTest.cc @@ -99,7 +99,7 @@ main(int argc, const char *argv[]) Arena arenaFile; - if (-1 == arenaFile.Create(ARENA_FILE, ARENA_SIZE, 0644)) { + if (-1 == arenaFile.Create(ARENA_FILE, ARENA_SIZE)) { abort(); } else if (!runSuite(arenaFile, "arenaFile")) { abort();