refactor: started refactoring trie api (contains segfaults)
parent
088322c18f
commit
50ebf86589
|
@ -4,7 +4,7 @@ project(lander C CXX)
|
||||||
set(CMAKE_C_STANDARD 17)
|
set(CMAKE_C_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
include_directories(crow/include trie/include)
|
include_directories(trie/include)
|
||||||
add_subdirectory(crow)
|
add_subdirectory(crow)
|
||||||
add_subdirectory(trie)
|
add_subdirectory(trie)
|
||||||
|
|
||||||
|
@ -16,5 +16,5 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(lander src/main.cpp)
|
add_executable(lander src/main.cpp)
|
||||||
target_link_libraries(lander PUBLIC trie)
|
target_link_libraries(lander PUBLIC Crow::Crow trie)
|
||||||
endif()
|
endif()
|
||||||
|
|
60
src/main.cpp
60
src/main.cpp
|
@ -7,6 +7,7 @@ extern "C" {
|
||||||
#include "trie.h"
|
#include "trie.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string file_path = "lander.data";
|
||||||
static const std::string index_page = R"(
|
static const std::string index_page = R"(
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -38,17 +39,18 @@ crow::response add_redirect(std::string base_url, Trie *trie, const char *url,
|
||||||
// The key already gets copied into the trie, so this pointer is safe to use
|
// The key already gets copied into the trie, so this pointer is safe to use
|
||||||
// ever after unlocking the trie
|
// ever after unlocking the trie
|
||||||
trie_wlock(trie);
|
trie_wlock(trie);
|
||||||
char *key = trie_add_random(trie, new_entry, secure);
|
char *key;
|
||||||
|
TrieExitCode res = trie_add_random(trie, &key, new_entry, secure);
|
||||||
trie_unlock(trie);
|
trie_unlock(trie);
|
||||||
|
|
||||||
if (key == NULL) {
|
if (res != Ok) {
|
||||||
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string res = base_url + key;
|
std::string out = base_url + key;
|
||||||
free(key);
|
free(key);
|
||||||
|
|
||||||
return crow::response(res);
|
return crow::response(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool store_paste(const char *key, const char *body) {
|
bool store_paste(const char *key, const char *body) {
|
||||||
|
@ -71,10 +73,11 @@ crow::response add_paste(std::string base_url, Trie *trie, const char *body,
|
||||||
Entry *new_entry = entry_new(Paste, "");
|
Entry *new_entry = entry_new(Paste, "");
|
||||||
|
|
||||||
trie_wlock(trie);
|
trie_wlock(trie);
|
||||||
char *key = trie_add_random(trie, new_entry, secure);
|
char *key;
|
||||||
|
TrieExitCode res = trie_add_random(trie, &key, new_entry, secure);
|
||||||
trie_unlock(trie);
|
trie_unlock(trie);
|
||||||
|
|
||||||
if (key == NULL) {
|
if (res != Ok) {
|
||||||
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,10 +85,10 @@ crow::response add_paste(std::string base_url, Trie *trie, const char *body,
|
||||||
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string res = base_url + key;
|
std::string out = base_url + key;
|
||||||
free(key);
|
free(key);
|
||||||
|
|
||||||
return crow::response(res);
|
return crow::response(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
@ -95,26 +98,21 @@ int main() {
|
||||||
ENV(api_key, "LANDER_API_KEY");
|
ENV(api_key, "LANDER_API_KEY");
|
||||||
ENV(base_url, "LANDER_BASE_URL");
|
ENV(base_url, "LANDER_BASE_URL");
|
||||||
|
|
||||||
// Initialize trie and populate from data file
|
std::cout << "Initializing trie from file '" << file_path << "'..."
|
||||||
Trie *trie = trie_init();
|
|
||||||
|
|
||||||
std::string file_path = "lander.data";
|
|
||||||
|
|
||||||
std::cout << "Populating trie from file '" << file_path << "'..."
|
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
// Web server hasn't started yet, so there's no point in locking the trie
|
// Initialize trie and populate from data file
|
||||||
int count = trie_populate(trie, file_path.c_str());
|
Trie *trie;
|
||||||
|
int res = trie_init(&trie, file_path.c_str());
|
||||||
|
|
||||||
if (count == -1) {
|
if (res != 0) {
|
||||||
std::cout << "An error occured while populating the trie." << std::endl;
|
std::cout << "An error occured while initializing the trie." << std::endl;
|
||||||
|
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
|
||||||
std::cout << "Added " << count << " (" << trie_size(trie)
|
|
||||||
<< ") entries to trie." << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout << "Added " << trie_size(trie) << " entries to trie." << std::endl;
|
||||||
|
|
||||||
// Create pastes directory if not present
|
// Create pastes directory if not present
|
||||||
// TODO don't just ignore errors here
|
// TODO don't just ignore errors here
|
||||||
mkdir("pastes", 0700);
|
mkdir("pastes", 0700);
|
||||||
|
@ -130,9 +128,10 @@ int main() {
|
||||||
.methods(crow::HTTPMethod::Get)(
|
.methods(crow::HTTPMethod::Get)(
|
||||||
[trie](crow::response &res, std::string key) {
|
[trie](crow::response &res, std::string key) {
|
||||||
trie_rlock(trie);
|
trie_rlock(trie);
|
||||||
Entry *entry = trie_search(trie, key.c_str());
|
Entry *entry;
|
||||||
|
TrieExitCode status = trie_search(trie, &entry, key.c_str());
|
||||||
|
|
||||||
if (entry != NULL) {
|
if (status == Ok) {
|
||||||
if (entry->type == Redirect) {
|
if (entry->type == Redirect) {
|
||||||
res.redirect(entry->string);
|
res.redirect(entry->string);
|
||||||
} else if (entry->type == Paste) {
|
} else if (entry->type == Paste) {
|
||||||
|
@ -173,14 +172,17 @@ int main() {
|
||||||
Entry *new_entry = entry_new(Redirect, req.body.c_str());
|
Entry *new_entry = entry_new(Redirect, req.body.c_str());
|
||||||
|
|
||||||
trie_wlock(trie);
|
trie_wlock(trie);
|
||||||
bool added = trie_add(trie, key.c_str(), new_entry);
|
TrieExitCode status = trie_add(trie, key.c_str(), new_entry);
|
||||||
trie_unlock(trie);
|
trie_unlock(trie);
|
||||||
|
|
||||||
if (!added) {
|
switch (status) {
|
||||||
|
case Ok:
|
||||||
|
return crow::response(base_url + key);
|
||||||
|
case AlreadyPresent:
|
||||||
return crow::response(crow::status::CONFLICT);
|
return crow::response(crow::status::CONFLICT);
|
||||||
|
default:
|
||||||
|
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return crow::response(base_url + key);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a new Paste with a short randomly generated key
|
// Add a new Paste with a short randomly generated key
|
||||||
|
@ -209,10 +211,10 @@ int main() {
|
||||||
|
|
||||||
Entry *new_entry = entry_new(Paste, "");
|
Entry *new_entry = entry_new(Paste, "");
|
||||||
trie_wlock(trie);
|
trie_wlock(trie);
|
||||||
bool added = trie_add(trie, key.c_str(), new_entry);
|
TrieExitCode status = trie_add(trie, key.c_str(), new_entry);
|
||||||
trie_unlock(trie);
|
trie_unlock(trie);
|
||||||
|
|
||||||
if (!added) {
|
if (status != Ok) {
|
||||||
return crow::response(crow::status::CONFLICT);
|
return crow::response(crow::status::CONFLICT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,23 +41,22 @@ typedef struct entry {
|
||||||
char *string;
|
char *string;
|
||||||
} Entry;
|
} Entry;
|
||||||
|
|
||||||
|
typedef enum trie_exit_code {
|
||||||
|
Ok = 0,
|
||||||
|
NotFound,
|
||||||
|
AlreadyPresent,
|
||||||
|
FileError
|
||||||
|
} TrieExitCode;
|
||||||
|
|
||||||
Entry *entry_new(EntryType type, const char *string);
|
Entry *entry_new(EntryType type, const char *string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate and initialize an empty Trie.
|
* Allocate & initialize a new trie, and populate it with the data from the
|
||||||
|
* given data file.
|
||||||
*
|
*
|
||||||
* @return a pointer to an empty Trie struct
|
* @return 0 if everything was successful, non-zero otherwise
|
||||||
*/
|
*/
|
||||||
Trie *trie_init();
|
TrieExitCode trie_init(Trie **trie_ptr, const char *file_path);
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate trie with entries stored in the given file.
|
|
||||||
*
|
|
||||||
* @param trie
|
|
||||||
* @param file_path path to file containing entries
|
|
||||||
* @return amount of entries added; -1 if an error occured
|
|
||||||
*/
|
|
||||||
int trie_populate(Trie *trie, const char *file_path);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* De-allocate a trie by freeing the memory occupied by this trie.
|
* De-allocate a trie by freeing the memory occupied by this trie.
|
||||||
|
@ -70,10 +69,11 @@ void trie_free(Trie *trie);
|
||||||
* Search for an entry in the trie.
|
* Search for an entry in the trie.
|
||||||
*
|
*
|
||||||
* @param trie
|
* @param trie
|
||||||
|
* @param entry_ptr pointer to Entry will be stored here, if found
|
||||||
* @param key key representing the entry
|
* @param key key representing the entry
|
||||||
* @return pointer to entry; NULL if not found
|
* @return 0 if the search was successful, 1 if not found
|
||||||
*/
|
*/
|
||||||
Entry *trie_search(Trie *trie, const char *key);
|
TrieExitCode trie_search(Trie *trie, Entry **entry_ptr, const char *key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a string to this trie.
|
* Add a string to this trie.
|
||||||
|
@ -81,12 +81,9 @@ Entry *trie_search(Trie *trie, const char *key);
|
||||||
* @param trie
|
* @param trie
|
||||||
* @param key key to represent entry with
|
* @param key key to represent entry with
|
||||||
* @param entry entry to add
|
* @param entry entry to add
|
||||||
* @return true if the trie was changed by this operation, false if it was
|
* @return 0 if added, 1 if already in trie, something else if other errors
|
||||||
* already present
|
|
||||||
*/
|
*/
|
||||||
bool trie_add(Trie *trie, const char *key, Entry *entry);
|
TrieExitCode trie_add(Trie *trie, const char *key, Entry *entry);
|
||||||
|
|
||||||
bool trie_add_no_lock(Trie *trie, const char *key, Entry *entry);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an entry by generating a random string as the key.
|
* Add an entry by generating a random string as the key.
|
||||||
|
@ -97,7 +94,8 @@ bool trie_add_no_lock(Trie *trie, const char *key, Entry *entry);
|
||||||
* @return pointer to the generated key. This pointer is safe to use after
|
* @return pointer to the generated key. This pointer is safe to use after
|
||||||
* unlocking the trie, and should be freed manually.
|
* unlocking the trie, and should be freed manually.
|
||||||
*/
|
*/
|
||||||
char *trie_add_random(Trie *trie, Entry *entry, bool secure);
|
TrieExitCode trie_add_random(Trie *trie, char **key_ptr, Entry *entry,
|
||||||
|
bool secure);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an entry from this trie given its key.
|
* Remove an entry from this trie given its key.
|
||||||
|
|
122
trie/src/trie.c
122
trie/src/trie.c
|
@ -15,39 +15,25 @@ typedef struct ttrie {
|
||||||
pthread_rwlock_t lock;
|
pthread_rwlock_t lock;
|
||||||
} Trie;
|
} Trie;
|
||||||
|
|
||||||
|
TrieExitCode trie_add_no_lock(Trie *trie, const char *key, Entry *entry);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate and initialize an empty Trie
|
* Allocate and initialize an empty Trie
|
||||||
*
|
*
|
||||||
* @return pointer to the empty Trie
|
* @return pointer to the empty Trie
|
||||||
*/
|
*/
|
||||||
Trie *trie_init() {
|
TrieExitCode trie_init(Trie **trie_ptr, const char *file_path) {
|
||||||
|
// Allocate & initialize trie
|
||||||
Trie *trie = calloc(1, sizeof(Trie));
|
Trie *trie = calloc(1, sizeof(Trie));
|
||||||
trie->root = tnode_init();
|
trie->root = tnode_init();
|
||||||
|
trie->file_path = strdup(file_path);
|
||||||
pthread_rwlock_init(&trie->lock, NULL);
|
pthread_rwlock_init(&trie->lock, NULL);
|
||||||
|
|
||||||
return trie;
|
// Populate trie with data from file
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* De-allocate a TernaryTree by freeing its entire underlying structure.
|
|
||||||
*
|
|
||||||
* @param trie trie to free
|
|
||||||
*/
|
|
||||||
void trie_free(Trie *trie) {
|
|
||||||
tnode_free(trie->root);
|
|
||||||
free(trie);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool trie_add_no_lock(Trie *trie, const char *key, Entry *entry);
|
|
||||||
|
|
||||||
int trie_populate(Trie *trie, const char *file_path) {
|
|
||||||
trie->file_path = strdup(file_path);
|
|
||||||
|
|
||||||
FILE *fp = fopen(file_path, "r");
|
FILE *fp = fopen(file_path, "r");
|
||||||
|
|
||||||
// TODO properly handle this
|
|
||||||
if (fp == NULL) {
|
if (fp == NULL) {
|
||||||
return -1;
|
return FileError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We read in lines of at most 8192 characters (sounds like enough)
|
// We read in lines of at most 8192 characters (sounds like enough)
|
||||||
|
@ -89,7 +75,19 @@ int trie_populate(Trie *trie, const char *file_path) {
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
return entries;
|
*trie_ptr = trie;
|
||||||
|
|
||||||
|
return Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-allocate a TernaryTree by freeing its entire underlying structure.
|
||||||
|
*
|
||||||
|
* @param trie trie to free
|
||||||
|
*/
|
||||||
|
void trie_free(Trie *trie) {
|
||||||
|
tnode_free(trie->root);
|
||||||
|
free(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct searchresult {
|
typedef struct searchresult {
|
||||||
|
@ -145,16 +143,16 @@ SearchResult trie_search_node(Trie *trie, const char *key) {
|
||||||
* @param string string to look up
|
* @param string string to look up
|
||||||
* @return true if the string is present in the trie, false otherwise
|
* @return true if the string is present in the trie, false otherwise
|
||||||
*/
|
*/
|
||||||
Entry *trie_search(Trie *trie, const char *key) {
|
TrieExitCode trie_search(Trie *trie, Entry **entry_ptr, const char *key) {
|
||||||
SearchResult res = trie_search_node(trie, key);
|
SearchResult res = trie_search_node(trie, key);
|
||||||
|
|
||||||
Entry *return_value = NULL;
|
if (res.child == NULL) {
|
||||||
|
return NotFound;
|
||||||
if (res.child != NULL) {
|
|
||||||
return_value = res.child->entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return return_value;
|
*entry_ptr = res.child->entry;
|
||||||
|
|
||||||
|
return Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,7 +163,7 @@ Entry *trie_search(Trie *trie, const char *key) {
|
||||||
* @return true if the string wasn't present in the trie and thus added, false
|
* @return true if the string wasn't present in the trie and thus added, false
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
bool trie_add_no_lock(Trie *trie, const char *string, Entry *entry) {
|
TrieExitCode trie_add_no_lock(Trie *trie, const char *string, Entry *entry) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
uint8_t offset;
|
uint8_t offset;
|
||||||
TrieNode **node_ptr = &(trie->root);
|
TrieNode **node_ptr = &(trie->root);
|
||||||
|
@ -207,7 +205,7 @@ bool trie_add_no_lock(Trie *trie, const char *string, Entry *entry) {
|
||||||
child_node->entry = entry;
|
child_node->entry = entry;
|
||||||
|
|
||||||
trie->size++;
|
trie->size++;
|
||||||
return true;
|
return Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (offset < (*child_node_ptr)->string_len) {
|
while (offset < (*child_node_ptr)->string_len) {
|
||||||
|
@ -258,43 +256,44 @@ bool trie_add_no_lock(Trie *trie, const char *string, Entry *entry) {
|
||||||
} while (string[i] != DELIMITER);
|
} while (string[i] != DELIMITER);
|
||||||
|
|
||||||
if ((*child_node_ptr)->represents) {
|
if ((*child_node_ptr)->represents) {
|
||||||
return false;
|
return AlreadyPresent;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*child_node_ptr)->represents = true;
|
(*child_node_ptr)->represents = true;
|
||||||
trie->size++;
|
trie->size++;
|
||||||
return true;
|
return Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool trie_add(Trie *trie, const char *key, Entry *entry) {
|
TrieExitCode trie_add(Trie *trie, const char *key, Entry *entry) {
|
||||||
if (trie->file_path != NULL) {
|
// Easiest way to make sure we don't add duplicate entries
|
||||||
// Easiest way to make sure we don't add duplicate entries
|
// We use an internal function that doesn't require a read lock, as we're
|
||||||
// We use an internal function that doesn't require a read lock, as we're
|
// already inside a write lock
|
||||||
// already inside a write lock
|
if (trie_search_node(trie, key).child != NULL) {
|
||||||
if (trie_search_node(trie, key).child != NULL) {
|
return AlreadyPresent;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *fp = fopen(trie->file_path, "a");
|
|
||||||
|
|
||||||
if (fp == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs(key, fp);
|
|
||||||
fputs(" ", fp);
|
|
||||||
fputc(entry_type_to_char(entry->type), fp);
|
|
||||||
fputs(" ", fp);
|
|
||||||
fputs(entry->string, fp);
|
|
||||||
fputs("\n", fp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function *should* always return true. Otherwise, the function would've
|
FILE *fp = fopen(trie->file_path, "a");
|
||||||
|
|
||||||
|
if (fp == NULL) {
|
||||||
|
return FileError;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs(key, fp);
|
||||||
|
fputs(" ", fp);
|
||||||
|
fputc(entry_type_to_char(entry->type), fp);
|
||||||
|
fputs(" ", fp);
|
||||||
|
fputs(entry->string, fp);
|
||||||
|
fputs("\n", fp);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
// This function *should* always return Ok. Otherwise, the function would've
|
||||||
// exited because the string was found in the trie.
|
// exited because the string was found in the trie.
|
||||||
return trie_add_no_lock(trie, key, entry);
|
return trie_add_no_lock(trie, key, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *trie_add_random(Trie *trie, Entry *entry, bool secure) {
|
TrieExitCode trie_add_random(Trie *trie, char **key_ptr, Entry *entry,
|
||||||
|
bool secure) {
|
||||||
// Generate random key
|
// Generate random key
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
int key_length = secure ? RANDOM_KEY_LENGTH_LONG : RANDOM_KEY_LENGTH_SHORT;
|
int key_length = secure ? RANDOM_KEY_LENGTH_LONG : RANDOM_KEY_LENGTH_SHORT;
|
||||||
|
@ -312,13 +311,11 @@ char *trie_add_random(Trie *trie, Entry *entry, bool secure) {
|
||||||
ok = trie_search_node(trie, key).child == NULL;
|
ok = trie_search_node(trie, key).child == NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool res = trie_add(trie, key, entry);
|
TrieExitCode return_value = trie_add(trie, key, entry);
|
||||||
char *return_value;
|
|
||||||
|
|
||||||
if (res) {
|
if (return_value == Ok) {
|
||||||
return_value = key;
|
*key_ptr = key;
|
||||||
} else {
|
} else {
|
||||||
return_value = NULL;
|
|
||||||
free(key);
|
free(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +395,4 @@ int trie_rlock(Trie *trie) { return pthread_rwlock_rdlock(&trie->lock); }
|
||||||
|
|
||||||
int trie_wlock(Trie *trie) { return pthread_rwlock_wrlock(&trie->lock); }
|
int trie_wlock(Trie *trie) { return pthread_rwlock_wrlock(&trie->lock); }
|
||||||
|
|
||||||
int trie_unlock(Trie *trie) {
|
int trie_unlock(Trie *trie) { return pthread_rwlock_unlock(&trie->lock); }
|
||||||
printf("sup\n");
|
|
||||||
return pthread_rwlock_unlock(&trie->lock);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue