Finish phonebook command and add to install targets.

This commit is contained in:
Kyle Isom 2023-10-10 14:01:09 -07:00
parent 30c586d37d
commit e3c95964b3
10 changed files with 297 additions and 41 deletions

View File

@ -123,25 +123,27 @@ Arena::Open(const char *path)
int 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; int newFileDes = 0;
if (this->size > 0) { if (this->size > 0) {
this->Destroy(); this->Destroy();
} }
newFileDes = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
if (newFileDes == -1) { fHandle = fopen(path, "w");
if (fHandle == nullptr) {
return -1; return -1;
} }
newFileDes = fileno(fHandle);
if (ftruncate(newFileDes, fileSize) == -1) { if (ftruncate(newFileDes, fileSize) == -1) {
return -1; return -1;
} }
close(newFileDes); close(newFileDes);
return this->Open(path); return this->Open(path);
} }
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32) #elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)

16
Arena.h
View File

@ -35,15 +35,6 @@
namespace klib { 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 /// \enum ArenaType
/// ///
@ -126,10 +117,9 @@ public:
/// ///
/// \param path The path to the file that should be created. /// \param path The path to the file that should be created.
/// \param fileSize The size of the file to create. /// \param fileSize The size of the file to create.
/// \param mode The permissions to load.
/// \return Returns 0 on success and -1 on error. /// \return Returns 0 on success and -1 on error.
#if defined(__linux__) #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) #elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
int Create(const char *path, size_t fileSize, DWORD mode); 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. /// \param path The path to the file to be loaded.
/// \return Returns 0 on success and -1 on error. /// \return Returns 0 on success and -1 on error.
#if defined(__linux__)
int Open(const char *path); 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. /// NewCursor returns a pointer to the start of the memory in the arena.
/// ///

View File

@ -34,9 +34,12 @@ set(SOURCE_FILES
Dictionary.cc Dictionary.cc
Exceptions.cpp Exceptions.cpp
Test.cc Test.cc
TLV.cc) TLV.cc
Commander.cpp
Commander.h)
add_library(klib STATIC ${SOURCE_FILES} ${HEADER_FILES}) add_library(klib STATIC ${SOURCE_FILES} ${HEADER_FILES})
add_executable(phonebook phonebook.cpp) add_executable(phonebook phonebook.cpp)
target_link_libraries(phonebook klib) target_link_libraries(phonebook klib)
@ -67,8 +70,9 @@ add_custom_target(cloc
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
configure_file(klib.pc.in klib.pc @ONLY) configure_file(klib.pc.in klib.pc @ONLY)
install(TARGETS klib LIBRARY DESTINATION ${PREFIX}/lib) install(TARGETS klib LIBRARY DESTINATION lib)
install(FILES ${HEADER_FILES} DESTINATION include/{klib}) install(TARGETS phonebook RUNTIME DESTINATION bin)
install(FILES ${HEADER_FILES} DESTINATION include/klib)
install(FILES klibConfig.cmake DESTINATION share/klib/cmake) install(FILES klibConfig.cmake DESTINATION share/klib/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/klib.pc DESTINATION lib/pkgconfig) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/klib.pc DESTINATION lib/pkgconfig)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/klib) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/klib)

78
Commander.cpp Normal file
View File

@ -0,0 +1,78 @@
///
/// \file Commander.cpp
/// \author kyle
/// \created 10/10/23
/// \brief
/// \section COPYRIGHT
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// 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 <iostream>
#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

59
Commander.h Normal file
View File

@ -0,0 +1,59 @@
///
/// \file Commander.h
/// \author kyle
/// \created 2023-10-10
/// \brief Subprogram tooling.
#include <map>
#include <functional>
#include <string>
#include <vector>
#ifndef KLIB_COMMANDER_H
#define KLIB_COMMANDER_H
namespace klib {
typedef std::function<bool(int, char **)> 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<std::string, Subcommand *> cmap;
};
} // klib
#endif //KLIB_COMMANDER_H

View File

@ -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 * uint8_t *
Dictionary::seek(const char *key, uint8_t klen) 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 bool
Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen) Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen)
{ {
@ -123,7 +138,6 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
TLV::Record rec; TLV::Record rec;
TLV::ReadFromMemory(rec, cursor); TLV::ReadFromMemory(rec, cursor);
os << "Dictionary KV pairs" << std::endl;
if (rec.Tag == TLV::TAG_EMPTY) { if (rec.Tag == TLV::TAG_EMPTY) {
os << "\t(NONE)" << std::endl; os << "\t(NONE)" << std::endl;
return os; return os;

View File

@ -91,6 +91,13 @@ public:
/// \return True if the key is in the Dictionary, otherwise false. /// \return True if the key is in the Dictionary, otherwise false.
bool Contains(const char *key, uint8_t klen); 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 /// DumpToFile is a wrapper aorund a call to Arena::Write on the
/// underlying Arena. /// underlying Arena.
@ -99,7 +106,7 @@ public:
/// \return 0 on success, -1 on failure. /// \return 0 on success, -1 on failure.
int DumpToFile(const char *path); 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 os The output stream to write to.
/// \param dictionary The dictionary to write out. /// \param dictionary The dictionary to write out.

View File

@ -45,7 +45,7 @@ main(int argc, const char *argv[])
std::cout << "TESTPROG: " << argv[0] << std::endl; std::cout << "TESTPROG: " << argv[0] << std::endl;
#if defined(__linux__) #if defined(__linux__)
if (arena.Create(ARENA_FILE, ARENA_SIZE, 0644) == -1) { if (arena.Create(ARENA_FILE, ARENA_SIZE) == -1) {
abort(); abort();
} }
#else #else

View File

@ -7,10 +7,96 @@
using namespace std; using namespace std;
#include "Arena.h" #include "Arena.h"
#include "Commander.h"
#include "Dictionary.h" #include "Dictionary.h"
using namespace klib; using namespace klib;
static const char *defaultPhonebook = "pb.dat"; 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 static void
usage(ostream &os, int exc) usage(ostream &os, int exc)
@ -19,9 +105,10 @@ usage(ostream &os, int exc)
os << "\nThe default filename is pb.dat.\n\n"; os << "\nThe default filename is pb.dat.\n\n";
os << "Usage:\n"; os << "Usage:\n";
os << "\tphonebook [-f file] list\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] del key\n";
os << "\tphonebook [-f file] has 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 << "\tphonebook [-f file] put key value\n";
os << "\n"; os << "\n";
@ -31,12 +118,9 @@ usage(ostream &os, int exc)
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
Arena arena;
Dictionary pb(arena);
char *pbFile = (char *)defaultPhonebook;
int optind = 1; int optind = 1;
for (optind; optind < argc; optind++) { for (optind = 1; optind < argc; optind++) {
auto arg = string(argv[optind]); auto arg = string(argv[optind]);
if (arg[0] != '-') break; if (arg[0] != '-') break;
if (arg == "-h") usage(cout, 0); if (arg == "-h") usage(cout, 0);
@ -53,18 +137,40 @@ main(int argc, char *argv[])
usage(cout, 0); 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++]); auto command = string(argv[optind++]);
Commander commander;
if (command == "list") { commander.Register(Subcommand("list", 0, listFiles));
cout << pb << "\n"; commander.Register(Subcommand("new", 1, newPhonebook));
} else if (command == "del") { 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();
}
} }

View File

@ -99,7 +99,7 @@ main(int argc, const char *argv[])
Arena arenaFile; Arena arenaFile;
if (-1 == arenaFile.Create(ARENA_FILE, ARENA_SIZE, 0644)) { if (-1 == arenaFile.Create(ARENA_FILE, ARENA_SIZE)) {
abort(); abort();
} else if (!runSuite(arenaFile, "arenaFile")) { } else if (!runSuite(arenaFile, "arenaFile")) {
abort(); abort();