Commit d652def6 authored by Naman Dixit's avatar Naman Dixit

Done, hopefully

parents
server/data_store
server/server
client/client
Team members:
Naman Dixit
19305R005
namandixit@cse.iitb.ac.in
Varad Bhatnagar
19305R005
varadhbhatnagar@cse.iitb.ac.in
Instructions:
Server:
Compilation: Run `make` in "server/" directory.
Execution: Run `./server` in "server/" directory.
It supports the following command line parameters:
1. -port=XXX (the port number on which the server
will listen)
2. -threadPoolSize=YYY (number of threads in the
thread pool)
3. -numSetsInCache=ZZZ (number of sets in the cache)
4. -sizeOfSet=AAA (associativity of sets)
Storage: The server stores the data in "${PWD}/data_store/" directory.
A file of special note is "${PWD}/data_store/kvstore.xml" which
contains an XML dump of the stored key-value pairs. This is the
file to be used for grading, and is generated in a lazy fashion
as instructed here:
moodle.iitb.ac.in/mod/forum/discuss.php?d=125057#p191609
Logs: When the server start, it prints a log message:
Log: Waiting for connection on port <port>...
When a new connection is established, it prints:
Log: Connection made: client_fd=<client_fd>
If there are any errors, it will quit after printing a message
prefixed with "Error:".
Client:
Compilation: Run `make` in "client/" directory.
Execution: The client can be launched inside "client/" directory
in either:
1. interactive mode: `./client -i`
2. batch mode: `./client <input_file> <output_file>`
It supports the following command line parameters:
1. -i (run in interactive mode)
2. -port=XXX (the port number on which the client
will connect)
Logs: When the server start, it prints a log message:
Log: Starting communication with server on port <port>...
If there are any errors, it will quit after printing a message
prefixed with "Error:". If wrong command line parameters are
given, it quits after printing a help message prefixed with
"Help:".
ClientSource := client.c
ClientTarget := client
.PHONY: all ClientTarget
all: ClientTarget
ClientTarget:
@echo "Building client..."
@gcc -g3 -O0 -fno-strict-aliasing -fwrapv -msse2 -I../common \
--std=c11 -DBUILD_INTERNAL -DBUILD_SLOW -D_POSIX_C_SOURCE=200809L \
-Wall -Wextra -Wpedantic -pedantic-errors -Werror \
$(ClientSource) -o ${ClientTarget} \
-Wl,-rpath=\$$ORIGIN -Wl,-z,origin -Wl,--enable-new-dtags \
-static-libgcc
/*
* Creator: Naman Dixit
* Notice: © Copyright 2019 Naman Dixit
*/
#include "aux.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <ctype.h>
#if 0
# define log(...) printf("Log: " __VA_ARGS__)
#else
# define log(...)
#endif
#include "xml.h"
Char* tokenGet(Char *line, Char separator, Size *position)
{
Char *elem = NULL;
Size i = *position;
Size line_len = strlen(line);
while ((i < line_len) && (line[i] != '\n') && (line[i] != separator)) {
sbufPrint(elem, "%c", line[i]);
i++;
}
sbufPrint(elem, "%c", '\0');
*position = i + 1;
return elem;
}
int main(int argc, char** argv)
{
B32 interactive_mode = false;
Char *port = NULL;
for (Size i = 1; i < (Size)argc; i++) {
Size arg_pos = 0;
if ((arg_pos = strprefix("-i", argv[i]))) {
interactive_mode = true;
} else if ((arg_pos = strprefix("--interactive", argv[i]))) {
interactive_mode = true;
} else if ((arg_pos = strprefix("-port=", argv[i]))) {
port = &(argv[i][arg_pos]);
}
}
if (port == NULL) port = "8080";
FILE *file_input = NULL, *file_output = NULL;
if (interactive_mode == false) {
if (argc != 3) {
printf("Help: Incorrect command line parameters\n");
printf("Help: See README.txt file for info on correct invocation\n");
exit(-1);
}
Char *file_input_name = argv[1];
Char *file_output_name = argv[2];
if((file_input = fopen(file_input_name, "r")) == NULL) {
fprintf(stderr, "Error: The input file %s cannot be opened\n", file_input_name);
exit(-1);
}
if((file_output = fopen(file_output_name, "w")) == NULL) {
fprintf(stderr, "Error: The output file %s cannot be opened\n", file_output_name);
exit(-1);
}
} else {
file_input = stdin;
file_output = stdout;
}
Sint sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints = {.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM};
struct addrinfo *result = NULL;
Sint s = getaddrinfo(NULL, "8080", &hints, &result);
if (s != 0) {
fprintf(stderr, "Error: getaddrinfo: %s\n", gai_strerror(s));
exit(-1);
}
while (connect(sock_fd, result->ai_addr, result->ai_addrlen) == -1) {
fprintf(stderr, "Error: Couldn't connect on port %s, trying again in one second...\n", port);
sleep(1);
}
printf("Log: Starting communication with server on port %s...\n", port);
while (true) {
if (interactive_mode == true) {
printf("===QUERY: ");
}
Char *command = NULL;
{ // Read command
Sint c = 0;
while (((c = getc(file_input)) != '\n') && (c != EOF)) {
sbufPrint(command, "%c", (Char)c);
}
if (interactive_mode && (c == EOF)) {
printf("\n");
break;
}
if (command == NULL) {
break;
}
sbufPrint(command, "%c", '\0');
}
if (interactive_mode == true) {
printf("RESPONSE: ");
}
Char *xml_request_message = NULL;
// Response message will be of proper legth, since server will make sure of that.
Size response_size = KiB(300);
Char *xml_response_message = calloc(response_size, sizeof(*xml_response_message));
Size index = 0;
Char *query_type = tokenGet(command, ',', &index);
Char *query_key = tokenGet(command, ',', &index);
if(strcmp(query_type, "GET") == 0) {
xml_request_message = xmlCreateMessage(XML_Message_Kind_GET,
query_key, NULL, NULL);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
} else if(strcmp(query_type, "SET") == 0) {
Char *query_value = tokenGet(command, '\n', &index);
xml_request_message = xmlCreateMessage(XML_Message_Kind_PUT,
query_key, query_value, NULL);
sbufDelete(query_value);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
} else if(strcmp(query_type,"DEL") == 0) {
xml_request_message = xmlCreateMessage(XML_Message_Kind_DELETE,
query_key, NULL, NULL);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
}
sbufDelete(query_type);
sbufDelete(query_key);
XML_Message msg = xmlParseMessage(xml_response_message);
switch (msg.kind) {
case XML_Message_Kind_RESP_GET: {
fprintf(file_output, "%s,%s\n", msg.key, msg.value);
} break;
case XML_Message_Kind_RESP_PUT_DELETE: {
fprintf(file_output, "success\n");
} break;
case XML_Message_Kind_RESP_ERROR: {
fprintf(file_output, "error,%s\n", msg.error);
} break;
default: {
} break;
}
sbufDelete(xml_request_message);
free(xml_response_message);
free(msg.key);
free(msg.value);
free(msg.error);
}
return 0;
}
#if !defined(AUX_H_INCLUDE_GUARD)
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdalign.h>
typedef int8_t S8;
typedef int16_t S16;
typedef int32_t S32;
typedef int64_t S64;
typedef int Sint;
typedef uint8_t U8;
typedef uint16_t U16;
typedef uint32_t U32;
typedef uint64_t U64;
typedef unsigned Uint;
typedef size_t Size;
typedef uintptr_t Uptr;
typedef intptr_t Sptr;
typedef ptrdiff_t Dptr;
typedef float F32;
typedef double F64;
typedef U8 B8;
typedef U16 B16;
typedef U32 B32;
typedef U64 B64;
# define true 1U
# define false 0U
typedef unsigned char Byte;
typedef char Char;
# define elemin(array) (sizeof(array)/sizeof((array)[0]))
# if !defined(max)
# define max(x, y) ((x) >= (y) ? (x) : (y))
# endif
# if !defined(min)
# define min(x, y) ((x) <= (y) ? (x) : (y))
# endif
# define KiB(x) ( (x) * 1024ULL)
# define MiB(x) (KiB(x) * 1024ULL)
# define GiB(x) (MiB(x) * 1024ULL)
# define TiB(x) (GiB(x) * 1024ULL)
# define THOUSAND 1000L
# define MILLION 1000000L
# define BILLION 1000000000L
# define unused_variable(var) (void)var
# define global_variable static
# define persistent_value static
# define internal_function static
# define header_function static inline
#define MEM_MAX_ALIGN_MINUS_ONE (alignof(max_align_t) - 1u)
#define memAlignUp(p) (((p) + MEM_MAX_ALIGN_MINUS_ONE) & (~ MEM_MAX_ALIGN_MINUS_ONE))
#define memAlignDown(p) (mem_ALIGN_UP((p) - MEM_MAX_ALIGN_MINUS_ONE))
typedef enum MemAllocMode {
MemAllocMode_NONE,
MemAllocMode_CREATE,
MemAllocMode_ALLOC,
MemAllocMode_REALLOC,
MemAllocMode_DEALLOC,
MemAllocMode_DEALLOC_ALL,
} MemAllocMode;
# define MEM_ALLOCATOR(allocator) \
void* allocator(MemAllocMode mode, \
Size size, void* old_ptr, \
void *data)
typedef MEM_ALLOCATOR(MemAllocator);
struct MemCRT_Header {
Size size;
};
# define memCRTAlloc(size) memCRT(MemAllocMode_ALLOC, size, NULL, NULL)
# define memCRTRealloc(ptr, size) memCRT(MemAllocMode_REALLOC, size, ptr, NULL)
# define memCRTDealloc(ptr) memCRT(MemAllocMode_DEALLOC, 0, ptr, NULL)
header_function
MEM_ALLOCATOR(memCRT)
{
unused_variable(data);
switch (mode) {
case MemAllocMode_CREATE: {
// NOTE(naman): Not needed for now
} break;
case MemAllocMode_ALLOC: {
Size memory_size = memAlignUp(size);
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Size total_size = memory_size + header_size;
Byte *mem = malloc(total_size);
struct MemCRT_Header *header = (struct MemCRT_Header *)mem;
header->size = memory_size;
Byte *result = mem + header_size;
memset(result, 0, memory_size);
return result;
} break;
case MemAllocMode_REALLOC: {
Size memory_size = memAlignUp(size);
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Size total_size = memory_size + header_size;
Byte *mem = malloc(total_size);
struct MemCRT_Header *header = (struct MemCRT_Header *)mem;
header->size = memory_size;
Byte *result = mem + header_size;
if (old_ptr != NULL) {
Byte *previous_mem = (Byte*)old_ptr - header_size;
struct MemCRT_Header *previous_header = (struct MemCRT_Header *)previous_mem;
Size previous_size = previous_header->size;
memcpy(result, old_ptr, previous_size);
memset(result + previous_size, 0, memory_size - previous_size);
memCRTDealloc(old_ptr);
} else {
memset(result, 0, memory_size);
}
return result;
} break;
case MemAllocMode_DEALLOC: {
if (old_ptr == NULL) {
return NULL;
}
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Byte *mem = (Byte*)old_ptr - header_size;
free(mem);
} break;
case MemAllocMode_DEALLOC_ALL: {
// TODO(naman): Maybe we should use a off-the-shelf malloc that allows this?
} break;
case MemAllocMode_NONE: {
} break;
}
return NULL;
}
/* ==============
* Strechy Buffer
*/
/* API ----------------------------------------
* Size sbufAdd (T *ptr, T elem)
* void sbufDelete (T *ptr)
* T* sbufEnd (T *ptr)
*
* Size sbufSizeof (T *ptr)
* Size sbufElemin (T *ptr)
* Size sbufMaxSizeof (T *ptr)
* Size sbufMaxElemin (T *ptr)
*/
typedef struct Sbuf_Header {
Size cap; // NOTE(naman): Maximum number of elements that can be stored
Size len; // NOTE(naman): Count of elements actually stored
Byte buf[];
} Sbuf_Header;
# define sbuf_GetHeader(sb) ((Sbuf_Header*)(void*)((Byte*)(sb) - offsetof(Sbuf_Header, buf)))
# define sbuf_Len(sb) ((sb) ? sbuf_GetHeader(sb)->len : 0U)
# define sbuf_Cap(sb) ((sb) ? sbuf_GetHeader(sb)->cap : 0U)
# define sbufAdd(sb, ...) ((sb) = sbuf_Grow((sb), sizeof(*(sb))), \
(sb)[sbuf_Len(sb)] = (__VA_ARGS__), \
((sbuf_GetHeader(sb))->len)++)
# define sbufDelete(sb) ((sb) ? \
(memCRTDealloc(sbuf_GetHeader(sb)), (sb) = NULL) : \
0)
# define sbufClear(sb) ((sb) ? \
(memset((sb), 0, sbufSizeof(sb)), \
sbuf_GetHeader(sb)->len = 0) : \
0)
# define sbufResize(sb, n) (((n) > sbufMaxElemin(sb)) ? \
((sb) = sbuf_Resize(sb, n, sizeof(*(sb)))) : \
0)
# define sbufSizeof(sb) (sbuf_Len(sb) * sizeof(*(sb)))
# define sbufElemin(sb) (sbuf_Len(sb))
# define sbufMaxSizeof(sb) (sbuf_Cap(sb) * sizeof(*(sb)))
# define sbufMaxElemin(sb) (sbuf_Cap(sb))
# define sbufEnd(sb) ((sb) + sbuf_Len(sb))
#define sbufPrint(sb, ...) ((sb) = sbuf_Print((sb), __VA_ARGS__))
#define sbufUnsortedDelete(sb, i, z) (((sb)[(i)] = (sb)[sbuf_Len(sb) - 1]), \
((sb)[sbuf_Len(sb) - 1] = (z)), \
((sbuf_GetHeader(sb)->len)--))
# if defined(COMPILER_CLANG)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-align"
# endif
header_function
void* sbuf_Grow (void *buf, Size elem_size)
{
if ((sbuf_Len(buf) + 1) <= sbuf_Cap(buf)) {
return buf;
} else {
Size new_cap = max(2 * sbuf_Cap(buf), 4);
Size new_size = (new_cap * elem_size) + sizeof(Sbuf_Header);
Sbuf_Header *new_header = NULL;
if (buf != NULL) {
new_header = memCRTRealloc(sbuf_GetHeader(buf), new_size);
} else {
new_header = memCRTAlloc(new_size);
}
new_header->cap = new_cap;
return new_header->buf;
}
}
header_function
void* sbuf_Resize (void *buf, Size elem_count, Size elem_size)
{
Size new_cap = elem_count;
Size new_size = (new_cap * elem_size) + sizeof(Sbuf_Header);
Sbuf_Header *new_header = NULL;
if (buf != NULL) {
new_header = memCRTRealloc(sbuf_GetHeader(buf), new_size);
} else {
new_header = memCRTAlloc(new_size);
}
new_header->cap = new_cap;
return new_header->buf;
}
header_function
Char* sbuf_Print(Char *buf, const Char *fmt, ...)
{
va_list args;
va_start(args, fmt);
Size cap = sbufMaxElemin(buf) - sbufElemin(buf);
Size n = 1 + vsnprintf(sbufEnd(buf), cap, fmt, args);
va_end(args);
if (n > cap) {
sbufResize(buf, n + sbufElemin(buf));
va_start(args, fmt);
size_t new_cap = sbufMaxElemin(buf) - sbufElemin(buf);
n = 1 + vsnprintf(sbufEnd(buf), new_cap, fmt, args);
va_end(args);
}
sbuf_GetHeader(buf)->len += (n - 1);
return buf;
}
header_function
U64 hashFNV1a (Char *str)
{
U64 hash = 0xcbf29ce484222325; // FNV_offset_basis
for (Size i = 0; str[i] != '\0'; i++) {
hash = hash ^ str[i];
hash = hash * 0x100000001b3; // FNV_prime
}
return hash;
}
/* __builtin_clzll(x) returns the leading number of 0-bits in x, starting from
* most significant position.
*
* If b is the bit-width of the number,
* p is the closest lower power of two and
* lz is the number of leading 0-bits; then
* then a number between 2^p and 2^(p+1) has the form: (b-p-1 0-bits) 1 (p bits)
*
* => lz = b-p-1
* => p = b-(lz+1)
*
* Thus, the rounded-down log2 of the number is b-(lz+1).
*/
header_function
U64 u64Log2(U64 x)
{
U64 result = 64ULL - ((U64)__builtin_clzll(x) + 1ULL);
return result;
}
/* Linear Congruential Generator
*
* If x is the last random number,
* m is a number greater than zero that is a power of two,
* a is a number between 0 and m,
* then the next random number is ((x * a) % m).
*
* Unfortunately, the lower bits don't have enought randomness in them. The LSB doesn't
* change at all, the second LSB alternates, the one after that toggles every 2 turns and so
* on. Therefore, we try to get rid of the LSBs by pulling in some MSBs.
*
* We do the multiplcation twice because Chi-Square Test indicated that this method
* gives better randomness. Don't ask.
*
* NOTE(naman): Seed should be an odd number or the randomness might drop drastically.
*/
header_function
U64 u64Rand (U64 seed)
{
U64 previous = seed;
if (previous == 0) {
// This seed has been tested and should be preferred in normal circumstances.
previous = 2531011ULL;
}
U64 a = 214013ULL;
__uint128_t product = (__uint128_t)previous * (__uint128_t)a;
U64 upper = product >> 64, lower = (U64)product;
U64 log_upper = u64Log2(upper);
U64 shift_amount = 64 - (log_upper + 1);
upper = (upper << shift_amount) | (lower >> log_upper);
U64 result = upper * a;
return result;
}
/* ==========================
* Pointer-Pointer Hash Map
*/
typedef struct Hashmap {
struct HashmapKeys {
U64 hash;
Uptr key;
} *keys;
Uptr *values;
MemAllocator *allocator;
Size total_slots, filled_slots;
U64 a, b, m; /* Hashing constants */
U64 r; /* Last random number */
} Hashmap;
typedef enum HM_Flag {
HM_Flag_EMPTY,
HM_Flag_FILLED,
HM_Flag_VACATED, // Previously filled
} HM_Flag;
/* API ---------------------------------------------------
* Hashmap hmCreate (MemAllocator allocator,
* Size min_slots);
* void hmDelete (Hashmap hm);
* void* hmInsert (Hashmap *hm, void *key, void *value);
* Uptr hmInsertI (Hashmap *hm, Uptr key, Uptr value);
* void* hmLookup (Hashmap *hm, void *key);
* Uptr hmLookupI (Hashmap *hm, Uptr key);
* void* hmRemove (Hashmap *hm, void *key);
* Uptr hmRemoveI (Hashmap *hm, Uptr key);
*/
#define hm_GetFlag(hash) ((hash) >> 62)
#define hm_GetHash(hash) ((hash) & 0x3FFFFFFFFFFFFFFFULL)
#define hm_SetFlag(hash, flag) (hm_GetHash(hash) | (((U64)(flag)) << 62))
/*
* https://en.wikipedia.org/wiki/Universal_hashing#Avoiding_modular_arithmetic
* w is number of bits in machine word (64 in our case)
* s is the number of buckets/bins (slots in the hash table) to which the
* universe of hashable objects is to be mapped
* m is log2(s) (=> m = 2^s) and is equal to the number of bits in the final hash
* a is a random odd positive integer < 2^w (fitting in w bits)
* b is a random non-negative integer < 2^(w-m) (fitting in (w-m) bits)
*
* r is the last random number generated and is just an implementation detail.
*/
header_function
void hm_UpdateConstants (Hashmap *hm)
{
hm->m = hm->m + 1;
hm->total_slots = 1ULL << (hm->m);
do {
hm->r = u64Rand(hm->r);
hm->a = hm->r;
} while ((hm->a & 0x01) != 0x01); // Make sure that hm.a is odd
hm->r = u64Rand(hm->r);
// b should be (64 - m) bits long
hm->b = hm->r >> hm->m;
}
header_function
U64 hm_Hash (Hashmap *hm, Uptr key)
{
U64 result = ((hm->a * key) + hm->b) & (0xFFFFFFFFFFFFFFFFULL >> (64 - hm->m));
return result;
}
header_function
Hashmap hmCreate (MemAllocator allocator,
Size min_slots)
{
Hashmap hm = {0};
hm.allocator = allocator;
hm.m = u64Log2(min_slots); // Log of closest lower power of two
// This will make m log of closest upper power of two
hm_UpdateConstants(&hm);
hm.keys = allocator(MemAllocMode_ALLOC,
(hm.total_slots) * sizeof(*(hm.keys)),
NULL, NULL);
hm.values = allocator(MemAllocMode_ALLOC,
(hm.total_slots) * sizeof(*(hm.values)),
NULL, NULL);
// For NULL keys
hm.keys[0].hash = hm_SetFlag(hm.keys[0].hash, HM_Flag_FILLED);
hm.filled_slots = 1;
return hm;
}
header_function
void hmDelete (Hashmap hm)
{
hm.allocator(MemAllocMode_DEALLOC, 0, hm.keys, NULL);
hm.allocator(MemAllocMode_DEALLOC, 0, hm.values, NULL);
}
header_function
Size hm_LinearProbeSearch (Hashmap *hm, Uptr key)
{
if (key == 0) return 0;
U64 hash = hm_Hash(hm, key);
U64 hash_real = hm_GetHash(hash);
Size index = 0, i = 0;
B32 found = false;
for (i = 0; !found && (i < hm->total_slots); ++i) {
index = (hash_real + i) % (hm->total_slots);
HM_Flag flag = hm_GetFlag(hm->keys[index].hash);
if ((flag == HM_Flag_FILLED) &&
(hm->keys[index].key == key)) {
found = true;
} else if (flag == HM_Flag_VACATED) {
continue;
} else if (flag == HM_Flag_EMPTY) {
break;
}
}
if (i == hm->total_slots) {
index = 0;
}
Size result = (found ? index : 0);
return result;
}
header_function
Uptr hm_LinearProbeInsertion (Hashmap *hm,
U64 hash, Uptr key, Uptr value)
{
Uptr result_value = value;
for (Size i = 0; i < hm->total_slots; ++i) {
Size index = (hash + i) % (hm->total_slots);
HM_Flag flag = hm_GetFlag(hm->keys[index].hash);
B32 fill_now = false;
switch (flag) {
case HM_Flag_FILLED: {
if (hm->keys[index].key == key) {
result_value = hm->values[index];
fill_now = true;
}
} break;
case HM_Flag_VACATED:
case HM_Flag_EMPTY: {
fill_now = true;
} break;
}
if (fill_now) {
hm->keys[index].key = key;
hm->values[index] = value;
hm->keys[index].hash = hm_SetFlag(hash, HM_Flag_FILLED);
break;
}
}
return result_value;
}
header_function
Uptr hmInsertI (Hashmap *hm, Uptr key, Uptr value)
{
if ((key == 0) || (value == 0)) return 0;
if ((2U * (hm->filled_slots)) > (hm->total_slots)) {
Size total_slots = hm->total_slots;
struct HashmapKeys *keys = hm->keys;
Uptr *values = hm->values;
hm_UpdateConstants(hm);
hm->keys = (hm->allocator)(MemAllocMode_ALLOC,
sizeof(*(hm->keys)) * hm->total_slots,
NULL, NULL);
hm->values = (hm->allocator)(MemAllocMode_ALLOC,
sizeof(*(hm->values)) * hm->total_slots,
NULL, NULL);
// For NULL keys
hm->keys[0].hash = hm_SetFlag(hm->keys[0].hash, HM_Flag_FILLED);
for (Size i = 1; i < total_slots; ++i) {
U64 hash_i_old = keys[i].hash;
Uptr key_i = keys[i].key;
Uptr value_i = values[i];
HM_Flag flag_i = hm_GetFlag(hash_i_old);
U64 hash_i_new = hm_Hash(hm, key_i);
if (flag_i == HM_Flag_FILLED) {
hm_LinearProbeInsertion(hm, hash_i_new, key_i, value_i);
}
}
(hm->allocator)(MemAllocMode_DEALLOC, 0, keys, NULL);
(hm->allocator)(MemAllocMode_DEALLOC, 0, values, NULL);
}
U64 hash = hm_Hash(hm, key);
Uptr result_value = hm_LinearProbeInsertion(hm, hash, key, value);
hm->filled_slots += 1;
return result_value;
}
# if defined(COMPILER_CLANG)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wbad-function-cast"
# endif
header_function
void* hmInsert (Hashmap *hm, void *key, void *value)
{
void *result_value = (void*)hmInsertI(hm, (Uptr)key, (Uptr)value);
return result_value;
}
header_function
Uptr hmLookupI (Hashmap *hm, Uptr key)
{
Size location = hm_LinearProbeSearch(hm, key);
Uptr result_value = hm->values[location];
return result_value;
}
header_function
void* hmLookup (Hashmap *hm, void *key)
{
void *result_value = (void*)hmLookupI(hm, (Uptr)key);
return result_value;
}
header_function
Uptr hmRemoveI (Hashmap *hm, Uptr key)
{
Size location = hm_LinearProbeSearch(hm, key);
Uptr result_value = 0;
if (location != 0) {
U64 hash = hm->keys[location].hash;
hm->keys[location].hash = hm_SetFlag(hash, HM_Flag_VACATED);
hm->filled_slots -= 1;
result_value = hm->values[location];
}
return result_value;
}
header_function
void* hmRemove (Hashmap *hm, void *key)
{
void *result_value = (void*)hmRemoveI(hm, (Uptr)key);
return result_value;
}
header_function
Size strprefix(Char *pre, Char *str)
{
Size lenpre = strlen(pre);
Size lenstr = strlen(str);
if (lenstr < lenpre) {
return 0;
} else {
if (memcmp(pre, str, lenpre) == 0) {
return lenpre;
} else {
return 0;
}
}
}
#define AUX_H_INCLUDE_GUARD
#endif
typedef enum XML_Message_Kind {
XML_Message_Kind_NONE,
XML_Message_Kind_GET,
XML_Message_Kind_PUT,
XML_Message_Kind_DELETE,
XML_Message_Kind_RESP_,
XML_Message_Kind_RESP_GET,
XML_Message_Kind_RESP_PUT_DELETE,
XML_Message_Kind_RESP_ERROR,
XML_Message_Kind_SYSTEM_ERROR,
XML_Message_Kind_TOTAL
} XML_Message_Kind;
typedef struct XML_Message {
XML_Message_Kind kind;
Char *key;
Char *value;
Char *error;
} XML_Message;
internal_function
XML_Message xmlParseMessage (Char *xml) {
Char *stream = xml;
XML_Message result = {0};
Char *error_message = "XML Error: Received unparseable message";
#define PARSE_CHAR(c) do { if (stream[0] != c) goto parse_failed; else stream++;} while (0)
#define PARSE_SPACE() do { while (isspace(stream[0])) { stream++; }} while (0)
PARSE_CHAR('<');
PARSE_CHAR('?');
PARSE_SPACE();
PARSE_CHAR('x');
PARSE_CHAR('m');
PARSE_CHAR('l');
PARSE_SPACE();
PARSE_CHAR('v');
PARSE_CHAR('e');
PARSE_CHAR('r');
PARSE_CHAR('s');
PARSE_CHAR('i');
PARSE_CHAR('o');
PARSE_CHAR('n');
PARSE_SPACE();
PARSE_CHAR('=');
PARSE_SPACE();
PARSE_CHAR('"');
PARSE_CHAR('1');
PARSE_CHAR('.');
PARSE_CHAR('0');
PARSE_CHAR('"');
PARSE_SPACE();
PARSE_CHAR('e');
PARSE_CHAR('n');
PARSE_CHAR('c');
PARSE_CHAR('o');
PARSE_CHAR('d');
PARSE_CHAR('i');
PARSE_CHAR('n');
PARSE_CHAR('g');
PARSE_SPACE();
PARSE_CHAR('=');
PARSE_SPACE();
PARSE_CHAR('"');
PARSE_CHAR('U');
PARSE_CHAR('T');
PARSE_CHAR('F');
PARSE_CHAR('-');
PARSE_CHAR('8');
PARSE_CHAR('"');
PARSE_SPACE();
PARSE_CHAR('?');
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('t');
PARSE_CHAR('y');
PARSE_CHAR('p');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('=');
PARSE_SPACE();
PARSE_CHAR('"');
Char kind_str[7] = {0};
Size kind_str_len = 0;
while (stream[0] != '"') {
kind_str[kind_str_len] = stream[0];
stream++;
if (kind_str_len >= 6) {
goto parse_failed;
}
kind_str_len++;
}
XML_Message_Kind kind = XML_Message_Kind_NONE;
if (strcmp(kind_str, "getreq") == 0) {
kind = XML_Message_Kind_GET;
} else if (strcmp(kind_str, "putreq") == 0) {
kind = XML_Message_Kind_PUT;
} else if (strcmp(kind_str, "delreq") == 0) {
kind = XML_Message_Kind_DELETE;
} else if (strcmp(kind_str, "resp") == 0) {
kind = XML_Message_Kind_RESP_;
} else {
goto parse_failed;
}
PARSE_CHAR('"');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
switch (kind) {
case XML_Message_Kind_GET: {
result.kind = kind;
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
Size key_len_max = 256;
result.key = calloc(key_len_max + 1, sizeof(*result.key));
Size key_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.key[key_len] = stream[0];
stream++;
if (key_len >= key_len_max) {
error_message = "Oversized key";
goto parse_failed;
}
key_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
} break;
case XML_Message_Kind_PUT: {
result.kind = kind;
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
Size key_len_max = 256;
result.key = calloc(key_len_max + 1, sizeof(*result.key));
Size key_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.key[key_len] = stream[0];
stream++;
if (key_len >= key_len_max) {
error_message = "Oversized key";
goto parse_failed;
}
key_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('V');
PARSE_CHAR('a');
PARSE_CHAR('l');
PARSE_CHAR('u');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
Size value_len_max = KiB(256);
result.value = calloc(value_len_max + 1, sizeof(*result.value));
Size value_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.value[value_len] = stream[0];
stream++;
if (value_len >= value_len_max) {
error_message = "Oversized value";
goto parse_failed;
}
value_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('V');
PARSE_CHAR('a');
PARSE_CHAR('l');
PARSE_CHAR('u');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
} break;
case XML_Message_Kind_DELETE: {
result.kind = kind;
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
Size key_len_max = 256;
result.key = calloc(key_len_max + 1, sizeof(*result.key));
Size key_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.key[key_len] = stream[0];
stream++;
if (key_len >= key_len_max) {
error_message = "Oversized key";
goto parse_failed;
}
key_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
} break;
case XML_Message_Kind_RESP_: {
PARSE_CHAR('<');
PARSE_SPACE();
switch (stream[0]) {
case 'K': {
result.kind = XML_Message_Kind_RESP_GET;
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
Size key_len_max = 256;
result.key = calloc(key_len_max + 1, sizeof(*result.key));
Size key_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.key[key_len] = stream[0];
stream++;
if (key_len >= key_len_max) {
error_message = "Oversized key";
goto parse_failed;
}
key_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('e');
PARSE_CHAR('y');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('V');
PARSE_CHAR('a');
PARSE_CHAR('l');
PARSE_CHAR('u');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
Size value_len_max = KiB(256);
result.value = calloc(value_len_max + 1, sizeof(*result.value));
Size value_len = 0;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
result.value[value_len] = stream[0];
stream++;
if (value_len >= value_len_max) {
error_message = "Oversized value";
goto parse_failed;
}
value_len++;
}
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('V');
PARSE_CHAR('a');
PARSE_CHAR('l');
PARSE_CHAR('u');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
} break;
case 'M': {
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
if ((stream[0] == 'S') &&
(stream[1] == 'u') &&
(stream[2] == 'c') &&
(stream[3] == 'c') &&
(stream[4] == 'e') &&
(stream[5] == 's') &&
(stream[6] == 's')) {
result.kind = XML_Message_Kind_RESP_PUT_DELETE;
PARSE_CHAR('S');
PARSE_CHAR('u');
PARSE_CHAR('c');
PARSE_CHAR('c');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
PARSE_SPACE();
PARSE_CHAR('<');
PARSE_SPACE();
PARSE_CHAR('/');
PARSE_SPACE();
PARSE_CHAR('K');
PARSE_CHAR('V');
PARSE_CHAR('M');
PARSE_CHAR('e');
PARSE_CHAR('s');
PARSE_CHAR('s');
PARSE_CHAR('a');
PARSE_CHAR('g');
PARSE_CHAR('e');
PARSE_SPACE();
PARSE_CHAR('>');
} else {
result.kind = XML_Message_Kind_RESP_ERROR;
Size error_len = 0;
Char *error_begin = stream;
while ((stream[0] != '<') || ((stream[0] == '<') && (stream[-1] == '\\'))) {
stream++;
error_len++;
}
result.error = calloc(error_len + 1, sizeof(*result.error));
for (Size i = 0; i < error_len; i++) {
result.error[i] = error_begin[i];
}
}
} break;
default: {
goto parse_failed;
}
}
} break;
default: {
goto parse_failed;
} break;
}
#undef PARSE_CHAR
#undef PARSE_SPACE
return result;
parse_failed:
free(result.key);
free(result.value);
free(result.error);
return (XML_Message){.kind = XML_Message_Kind_SYSTEM_ERROR, .error = strdup(error_message)};
}
internal_function
Char* xmlCreateMessage (XML_Message_Kind kind,
Char *key, Char *value, Char *error) {
Char *message = NULL;
switch (kind) {
case XML_Message_Kind_GET: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"getreq\">\n"
"<Key>");
sbufPrint(message, "%s", key);
sbufPrint(message,
"</Key>\n"
"</KVMessage>\n");
} break;
case XML_Message_Kind_PUT: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"putreq\">\n"
"<Key>");
sbufPrint(message, "%s", key);
sbufPrint(message,
"</Key>\n"
"<Value>");
sbufPrint(message, "%s", value);
sbufPrint(message,
"</Value>\n"
"</KVMessage>\n");
} break;
case XML_Message_Kind_DELETE: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"delreq\">\n"
"<Key>");
sbufPrint(message, "%s", key);
sbufPrint(message,
"</Key>\n"
"</KVMessage>\n");
} break;
case XML_Message_Kind_RESP_GET: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"resp\">\n"
"<Key>");
sbufPrint(message, "%s", key);
sbufPrint(message,
"</Key>\n"
"<Value>");
sbufPrint(message, "%s", value);
sbufPrint(message,
"</Value>\n"
"</KVMessage>\n");
} break;
case XML_Message_Kind_RESP_PUT_DELETE: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"resp\">\n"
"<Message>Success</Message>\n"
"</KVMessage>\n");
} break;
case XML_Message_Kind_RESP_ERROR: {
sbufPrint(message,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<KVMessage type=\"resp\">\n"
"<Message>");
sbufPrint(message, "%s", error);
sbufPrint(message,
"</Message>\n"
"</KVMessage>\n");
} break;
default: {
message = NULL;
} break;
}
return message;
}
ServerSource := server.c
ServerTarget := server
.PHONY: all ServerTarget
all: ServerTarget
ServerTarget:
@echo "Building server..."
@gcc -g3 -O0 -fno-strict-aliasing -fwrapv -msse2 -I../common \
--std=c11 -DBUILD_INTERNAL -DBUILD_SLOW -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE \
-Wall -Wextra -Wpedantic -pedantic-errors -Werror \
$(ServerSource) -o $(ServerTarget) \
-Wl,-rpath=\$$ORIGIN -Wl,-z,origin -Wl,--enable-new-dtags \
-static-libgcc -pthread
typedef struct Cache_Slot {
Char *key;
Char *value;
B8 valid;
B8 chance;
} Cache_Slot;
typedef struct Cache_Bucket {
pthread_mutex_t lock;
Cache_Slot *slots;
} Cache_Bucket;
typedef struct Cache {
Size bucket_count;
Size slot_count;
Size associativity;
Cache_Bucket buckets[];
} Cache;
global_variable Cache *global_cache;
internal_function
void cacheInit (Size bucket_count, Size associativity)
{
Size cache_size = sizeof(*global_cache) + (bucket_count * sizeof(global_cache->buckets[0]));
global_cache = malloc(cache_size);
memset(global_cache, 0, cache_size);
for (Size i = 0; i < bucket_count; i++) {
global_cache->buckets[i].slots = calloc(associativity, sizeof(global_cache->buckets[i].slots[0]));
pthread_mutex_init(&(global_cache->buckets[i].lock), NULL);
}
global_cache->bucket_count = bucket_count;
global_cache->associativity = associativity;
global_cache->slot_count = bucket_count * associativity;
}
internal_function
Size cacheGetBucket (U64 hash)
{
return hash >> (64 - u64Log2(global_cache->bucket_count));
}
internal_function
void cacheInvalidateSlot (Size bucket, Size slot)
{
pthread_mutex_lock(&(global_cache->buckets[bucket].lock));
global_cache->buckets[bucket].slots[slot].valid = false;
free(global_cache->buckets[bucket].slots[slot].key);
free(global_cache->buckets[bucket].slots[slot].value);
global_cache->buckets[bucket].slots[slot].key = NULL;
global_cache->buckets[bucket].slots[slot].value = NULL;
pthread_mutex_unlock(&(global_cache->buckets[bucket].lock));
}
internal_function
Cache_Slot cacheGetSlot (Size bucket, Size slot)
{
Cache_Slot result = {0};
if (global_cache->buckets[bucket].slots[slot].valid) {
result = global_cache->buckets[bucket].slots[slot];
}
return result;
}
internal_function
Char* cacheGetKV (U64 hash, Char *key, Size *slotptr)
{
Size bucket = cacheGetBucket(hash);
for (Size i = 0; i < global_cache->associativity; i++) {
Cache_Slot cs = cacheGetSlot(bucket, i);
if (cs.valid && (strcmp(cs.key, key) == 0)) {
if (slotptr != NULL) {
*slotptr = i;
}
return cs.value;
}
}
return NULL;
}
internal_function
void cacheSetKV (U64 hash, Char *key, Char *value)
{
Size bucket = cacheGetBucket(hash);
pthread_mutex_lock(&(global_cache->buckets[bucket].lock));
Cache_Slot *slots = global_cache->buckets[bucket].slots;
// If data already present in cache
for (Size i = 0; i < global_cache->associativity; i++) {
if(slots[i].valid && (strcmp(slots[i].key, key) == 0)) {
free(slots[i].value);
slots[i].value = strdup(value);
slots[i].chance = true;
pthread_mutex_unlock(&(global_cache->buckets[bucket].lock));
return;
}
}
// Else if, there is an empty slot
for (Size i = 0; i < global_cache->associativity; i++) {
if (slots[i].valid == false) {
free(slots[i].key);
slots[i].key = strdup(key);
free(slots[i].value);
slots[i].value = strdup(value);
slots[i].valid = true;
slots[i].chance = true;
pthread_mutex_unlock(&(global_cache->buckets[bucket].lock));
return;
}
}
// Else, evict using second chance algorithm
while (true) {
for (Size i = 0; i < global_cache->associativity; i++) {
if(slots[i].chance == false) {
free(slots[i].key);
slots[i].key = strdup(key);
free(slots[i].value);
slots[i].value = strdup(value);
slots[i].valid = true;
slots[i].chance = true;
pthread_mutex_unlock(&(global_cache->buckets[bucket].lock));
return;
} else {
slots[i].chance = false;
}
}
}
pthread_mutex_unlock(&(global_cache->buckets[bucket].lock));
}
void cacheClear (void)
{
for (Size i = 0; i < global_cache->bucket_count; i++) {
for (Size j = 0; j < global_cache->associativity; j++) {
free(global_cache->buckets[i].slots[j].key);
free(global_cache->buckets[i].slots[j].value);
global_cache->buckets[i].slots[j].valid = false;
global_cache->buckets[i].slots[j].chance = false;
}
}
}
typedef struct Command {
struct Command *next;
union {
struct {
Char *key;
} get;
struct {
Char *key;
Char *value;
} put;
struct {
Char *key;
} del;
};
enum Command_Kind {
Command_Kind_NONE,
Command_Kind_GET,
Command_Kind_PUT,
Command_Kind_DEL,
} kind;
Sint fd;
} Command;
global_variable Command *global_command_first, *global_command_last, *global_command_divider;
global_variable pthread_mutex_t global_command_lock = PTHREAD_MUTEX_INITIALIZER;
global_variable pthread_cond_t global_command_cond_var = PTHREAD_COND_INITIALIZER;
internal_function
void commandInitBuffer (void)
{
global_command_first = calloc(1, sizeof(*global_command_first));
global_command_last = global_command_first;
global_command_divider = global_command_first;
return;
}
internal_function
void commandEnqueue (Command c)
{
global_command_last->next = calloc(1, sizeof(*(global_command_last->next)));
*(global_command_last->next) = c;
global_command_last = global_command_last->next;
while (global_command_first != global_command_divider) {
Command *temp = global_command_first;
global_command_first = global_command_first->next;
free(temp);
}
pthread_cond_broadcast(&global_command_cond_var);
return;
}
internal_function
void commandDequeue (Command *c)
{
pthread_mutex_lock(&global_command_lock);
while (global_command_divider == global_command_last) {
pthread_cond_wait(&global_command_cond_var, &global_command_lock);
}
*c = *(global_command_divider->next);
global_command_divider = global_command_divider->next;
pthread_mutex_unlock(&global_command_lock);
}
internal_function
void* commandProcessLoop (void *arg)
{
Thread_Pool_Metadata *data = arg;
XML_Dump_Metadata *xml_dump = data->xml_dump;
while (true) {
Command command = {0};
commandDequeue(&command);
switch (command.kind) {
case Command_Kind_GET: {
Char *error = NULL;
U64 hash = hashFNV1a(command.get.key);
// pthread_mutex_lock(&data_lock);
Char *value = storageGetFileData(hash, command.get.key, &error);
// pthread_mutex_unlock(&data_lock);
Result result = {0};
if (value == NULL) {
result = (Result){.kind = Result_Kind_ERROR,
.fd = command.fd,
.error.error = error};
} else {
result = (Result){.kind = Result_Kind_GET,
.fd = command.fd,
.get.key = command.get.key,
.get.value = value};
}
resultEnqueue(result);
} break;
case Command_Kind_PUT: {
Char *error = NULL;
U64 hash = hashFNV1a(command.put.key);
// pthread_mutex_lock(&data_lock);
B32 success = storageSetFileData(hash, command.put.key, command.put.value,
&error);
// pthread_mutex_unlock(&data_lock);
Result result = {0};
if (success == false) {
result = (Result){.kind = Result_Kind_ERROR,
.fd = command.fd,
.error.error = error};
} else {
pthread_cancel(xml_dump->thread);
pthread_join(xml_dump->thread, NULL);
fclose(xml_dump->file);
xml_dump->file = fopen(xml_dump->file_path, "w");
pthread_create(&(xml_dump->thread), NULL, &dump, xml_dump);
result = (Result){.kind = Result_Kind_PUT,
.fd = command.fd};
}
resultEnqueue(result);
} break;
case Command_Kind_DEL: {
Char *error = NULL;
U64 hash = hashFNV1a(command.del.key);
// pthread_mutex_lock(&data_lock);
B32 success = storageDeleteFileData(hash, command.del.key, &error);
// pthread_mutex_unlock(&data_lock);
Result result = {0};
if (success == false) {
result = (Result){.kind = Result_Kind_ERROR,
.fd = command.fd,
.error.error = error};
} else {
pthread_cancel(xml_dump->thread);
pthread_join(xml_dump->thread, NULL);
fclose(xml_dump->file);
xml_dump->file = fopen(xml_dump->file_path, "w");
pthread_create(&(xml_dump->thread), NULL, &dump, xml_dump);
result = (Result){.kind = Result_Kind_DEL,
.fd = command.fd};
}
resultEnqueue(result);
} break;
default: {
} break;
}
}
return NULL;
}
internal_function
void* dump (void *arg)
{
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
XML_Dump_Metadata *xml_dump = arg;
fprintf(xml_dump->file,
"<!--This XML file is generated in a lazy fashion, when the server is in a \n"
"quiscent state. This means that this file may end up being incomplete if\n"
"the server is killed forcefully before this dump is complete. -->\n\n"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<KVStore>\n");
fflush(xml_dump->file);
DIR *dir0 = opendir(".");
if (dir0 == NULL) {
perror("Can't open directory for xml dump");
return NULL;
}
for (struct dirent *entry1 = readdir(dir0); entry1 != NULL; entry1 = readdir(dir0)) {
Char path1[300] = {0};
strcat(path1, entry1->d_name);
strcat(path1, "/");
DIR *dir1 = opendir(path1);
if (strcmp(entry1->d_name, ".") == 0 || strcmp(entry1->d_name, "..") == 0) continue;
if (entry1->d_type != DT_DIR) continue;
for (struct dirent *entry2 = readdir(dir1); entry2 != NULL; entry2 = readdir(dir1)) {
Char path2[300] = {0};
strcat(path2, path1);
strcat(path2, entry2->d_name);
strcat(path2, "/");
DIR *dir2 = opendir(path2);
if (strcmp(entry2->d_name, ".") == 0 || strcmp(entry2->d_name, "..") == 0) continue;
for (struct dirent *entry3 = readdir(dir2); entry3 != NULL; entry3 = readdir(dir2)) {
Char path3[300] = {0};
strcat(path3, path2);
strcat(path3, entry3->d_name);
strcat(path3, "/");
DIR *dir3 = opendir(path3);
if (strcmp(entry3->d_name, ".") == 0 || strcmp(entry3->d_name, "..") == 0) continue;
for (struct dirent *entry4 = readdir(dir3); entry4 != NULL; entry4 = readdir(dir3)) {
Char path4[300] = {0};
strcat(path4, path3);
strcat(path4, entry4->d_name);
strcat(path4, "/");
DIR *dir4 = opendir(path4);
if (strcmp(entry4->d_name, ".") == 0 || strcmp(entry4->d_name, "..") == 0) continue;
for (struct dirent *entry_f = readdir(dir4); entry_f != NULL; entry_f = readdir(dir4)) {
if (strcmp(entry_f->d_name, ".") == 0 || strcmp(entry_f->d_name, "..") == 0) continue;
Char path_f[300] = {0};
strcat(path_f, path4);
strcat(path_f, entry_f->d_name);
Char *value = calloc((KiB(256) + 1), sizeof(*value));
FILE* value_file = fopen(path_f, "r");
fseek(value_file, 0L, SEEK_END);
Size size = ftell(value_file);
fseek(value_file, 0L, SEEK_SET);
fread(value, size, sizeof(*value), value_file);
fclose(value_file);
fprintf(xml_dump->file, "\t<KVPair>\n\t\t<Key>​%s​</Key>\n\t\t<Value>%s</Value>\n\t</KVPair>\n",
entry_f->d_name, value);
fflush(xml_dump->file);
free(value);
}
closedir(dir4);
}
closedir(dir3);
}
closedir(dir2);
}
closedir(dir1);
}
fprintf(xml_dump->file, "</KVStore>");
fflush(xml_dump->file);
closedir(dir0);
return NULL;
}
typedef struct Result {
struct Result *next;
union {
struct {
Char *value;
Char *key;
} get;
struct {
Char *error;
} error;
};
enum Result_Kind {
Result_Kind_NONE,
Result_Kind_GET,
Result_Kind_PUT,
Result_Kind_DEL,
Result_Kind_ERROR
} kind;
Sint fd;
} Result;
global_variable Result *global_result_first, *global_result_last, *global_result_divider;
global_variable pthread_mutex_t global_result_lock = PTHREAD_MUTEX_INITIALIZER;
internal_function
void resultInitBuffer (void)
{
global_result_first = calloc(1, sizeof(*global_result_first));
global_result_last = global_result_first;
global_result_divider = global_result_first;
}
internal_function
void resultEnqueue (Result c)
{
pthread_mutex_lock(&global_result_lock);
global_result_last->next = calloc(1, sizeof(*(global_result_last->next)));
*(global_result_last->next) = c;
global_result_last = global_result_last->next;
while (global_result_first != global_result_divider) {
Result *temp = global_result_first;
global_result_first = global_result_first->next;
free(temp);
}
pthread_mutex_unlock(&global_result_lock);
return;
}
internal_function
B32 resultDequeue (Result *c)
{
if (global_result_divider != global_result_last) {
*c = *(global_result_divider->next);
global_result_divider = global_result_divider->next;
return true;
}
return false;
}
/* The network code was inspired by the following blog post:
* https://eli.thegreenplace.net/2017/concurrent-servers-part-3-event-driven
*/
#include "aux.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <pthread.h>
#include <dirent.h>
#if 0
# define log(...) do {printf("Log: " __VA_ARGS__); fflush(stdout);} while(0)
#else
# define log(...)
#endif
typedef struct XML_Dump_Metadata {
FILE *file;
Char *file_path;
pthread_t thread;
} XML_Dump_Metadata;
typedef struct Thread_Pool_Metadata {
XML_Dump_Metadata *xml_dump;
} Thread_Pool_Metadata;
#include "xml.h"
#include "dump.c"
#include "cache.c"
#include "storage.c"
#include "result_buffer.c"
#include "command_buffer.c"
#define MAX_SOCKET_CONNECTIONS_REQUEST 64
#define MAX_EPOLL_EVENTS 1024
typedef struct Output_Resume {
Char *output;
Size output_pos;
Size output_len;
Sint fd;
} Output_Resume;
Sint main (Sint argc, Char *argv[])
{
Char *port = NULL;
Size thread_pool_size = 0;
Size cache_set_count = 0;
Size cache_associativity = 0;
for (Size i = 1; i < (Size)argc; i++) {
Size arg_pos = 0;
if ((arg_pos = strprefix("-port=", argv[i]))) {
port = &(argv[i][arg_pos]);
} else if ((arg_pos = strprefix("-threadPoolSize=", argv[i]))) {
thread_pool_size = (Size)strtol(&(argv[i][arg_pos]), NULL, 0);
} else if ((arg_pos = strprefix("-numSetsInCache=", argv[i]))) {
cache_set_count = (Size)strtol(&(argv[i][arg_pos]), NULL, 0);
} else if ((arg_pos = strprefix("-sizeOfSet=", argv[i]))) {
cache_associativity = (Size)strtol(&(argv[i][arg_pos]), NULL, 0);
}
}
if (port == NULL) port = "8080";
if (thread_pool_size == 0) thread_pool_size = 100;
if (cache_set_count == 0) cache_set_count = 32;
if (cache_associativity == 0) cache_associativity = 2;
cacheInit(cache_set_count, cache_associativity);
struct stat st = {0};
if(stat("data_store", &st) == -1) {
if(mkdir("data_store", 0777) < 0) {
fprintf(stderr, "Error: Cannot create directory %s because: ",
"data_store");
perror("");
exit(-1);
}
}
if(chdir("data_store") < 0) {
fprintf(stderr, "Error: Cannot change to directory %s because: ",
"data_store");
perror("");
exit(-1);
}
resultInitBuffer();
commandInitBuffer();
XML_Dump_Metadata xml_dump = {.file_path = "kvstore.xml"};
xml_dump.file = fopen(xml_dump.file_path, "w");
pthread_create(&(xml_dump.thread), NULL, &dump, &xml_dump);
pthread_t *threads = calloc(thread_pool_size, sizeof(*threads));
Thread_Pool_Metadata thread_metadata = {.xml_dump = &xml_dump};
for (Size i = 0; i < thread_pool_size; i++) {
pthread_create(&threads[i], NULL, &commandProcessLoop, &thread_metadata);
}
Hashmap output_map = hmCreate(memCRT, 1024);
Hashmap busy_map = hmCreate(memCRT, 1024);
// NOTE(naman): Create a socket for IPv4 and TCP.
Sint sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("ERROR opening socket");
exit(-1);
}
// NOTE(naman): This helps avoid spurious EADDRINUSE when the previous instance of this
// server died.
int opt = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
exit(-1);
}
// NOTE(naman): Get actual internet address to bind to using IPv4 and TCP,
// and listening passively
struct addrinfo hints = {.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE};
struct addrinfo *addrinfo = NULL;
Sint s = getaddrinfo(NULL, port, &hints, &addrinfo);
if (s != 0) {
fprintf(stderr, "Error: getaddrinfo: %s\n", gai_strerror(s));
exit(-1);
}
// NOTE(naman): Assign an address to the socket
if (bind(sock_fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) {
perror("bind()");
exit(-1);
}
// NOTE(naman): Start listening for incoming connections
if (listen(sock_fd, MAX_SOCKET_CONNECTIONS_REQUEST) != 0) {
perror("listen()");
exit(-1);
}
// NOTE(naman): Set the socket as non-blocking
int flags = fcntl(sock_fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(-1);
}
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL O_NONBLOCK");
exit(-1);
}
// NOTE(naman): Create an epoll instance, with no flags set.
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("epoll_create1");
exit(-1);
}
// NOTE(naman): Add the socket's file descriptor to the epoll set
struct epoll_event accept_event = {.data.fd = sock_fd,
.events = EPOLLIN};
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &accept_event) < 0) {
perror("epoll_ctl EPOLL_CTL_ADD");
exit(-1);
}
struct epoll_event* events = calloc(MAX_EPOLL_EVENTS, sizeof(struct epoll_event));
if (events == NULL) {
fprintf(stderr, "Unable to allocate memory for epoll_events");
exit(-1);
}
printf("Log: Waiting for connection on port %s...\n", port);
while (true) {
// NOTE(naman): Get the fd's that are ready
int nready = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1);
for (int i = 0; i < nready; i++) {
if (events[i].events & EPOLLERR) {
perror("epoll_wait returned EPOLLERR");
exit(-1);
}
if (events[i].data.fd == sock_fd) {
// NOTE(naman): A new client is connecting.
struct sockaddr_in peer_addr = {0};
socklen_t peer_addr_len = sizeof(peer_addr);
int accept_fd = accept(sock_fd, (struct sockaddr*)&peer_addr,
&peer_addr_len);
if (accept_fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// This can happen due to the nonblocking socket mode; in this
// case don't do anything, but print a notice (since these events
// are extremely rare and interesting to observe...)
fprintf(stderr, "accept() returned %s\n",
errno == EAGAIN ? "EAGAIN" : "EWOULDBLOCK");
} else {
perror("accept");
exit(-1);
}
} else {
printf("Log: Connection made: client_fd=%d\n", accept_fd);
// NOTE(naman): Set the socket as non-blocking
int flags = fcntl(sock_fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl F_GETFL");
exit(-1);
}
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL O_NONBLOCK");
exit(-1);
}
// NOTE(naman): Add the new file descriptor to the epoll set
struct epoll_event event = {.data.fd = accept_fd,
.events = EPOLLIN};
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_ADD");
exit(-1);
}
}
} else {
// A peer socket is ready.
if (events[i].events & EPOLLIN) {
// Ready for reading.
int fd = events[i].data.fd;
char *buffer = calloc(KiB(300), sizeof(*buffer));
int len = read(fd, buffer, KiB(300) - 1);
if (len == 0) {
close(fd);
continue;
}
if (hmLookupI(&busy_map, fd)) {
continue;
}
hmInsertI(&busy_map, fd, true);
XML_Message msg = xmlParseMessage(buffer);
free(buffer);
switch (msg.kind) {
case XML_Message_Kind_GET: {
Command command = {.fd = fd};
command.kind = Command_Kind_GET;
command.get.key = msg.key;
commandEnqueue(command);
} break;
case XML_Message_Kind_PUT: {
Command command = {.fd = fd};
command.kind = Command_Kind_PUT;
command.put.key = msg.key;
command.put.value = msg.value;
commandEnqueue(command);
} break;
case XML_Message_Kind_DELETE: {
Command command = {.fd = fd};
command.kind = Command_Kind_DEL;
command.del.key = msg.key;
commandEnqueue(command);
} break;
case XML_Message_Kind_SYSTEM_ERROR: {
Char *output = xmlCreateMessage(XML_Message_Kind_RESP_ERROR,
NULL, NULL, msg.error);
Size output_len = strlen(output);
Output_Resume *or = calloc(1, sizeof(*or));
or->fd = fd;
or->output = output;
or->output_pos = 0;
or->output_len = output_len;
hmInsertI(&output_map, fd, (Uptr)or);
} break;
default: {
} break;
}
struct epoll_event event = {.data.fd = fd,
.events = EPOLLIN | EPOLLOUT};
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_MOD");
exit(-1);
}
} else if (events[i].events & EPOLLOUT) {
// Writing into fd in which we previously were not able to finish writing to
int fd = events[i].data.fd;
Output_Resume *or = hmLookup(&output_map, (void*)(Uptr)fd);
if (or == NULL) {
// fprintf(stderr, "hmLookup returned NULL");
continue;
} else {
Char *output = or->output;
Size output_len = or->output_len;
Size output_pos = or->output_pos;
int nsent = write(or->fd, output + output_pos, output_len - output_pos);
if (nsent == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Try next time
} else {
perror("write() failed");
exit(-1);
}
} else if ((Size)nsent < (output_len - output_pos)) {
or->output_pos += nsent;
} else {
sbufDelete(output);
hmRemove(&output_map, (void*)(Uptr)fd);
hmRemoveI(&busy_map, fd);
free(or);
struct epoll_event event = {.data.fd = fd,
.events = EPOLLIN};
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD,
or->fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_MOD");
exit(-1);
}
}
}
}
}
}
Result result = {0};
while (resultDequeue(&result)) {
Char *key = NULL, *value = NULL, *error = NULL;
XML_Message_Kind xml_kind = XML_Message_Kind_NONE;
switch (result.kind) {
case Result_Kind_GET: {
key = result.get.key;
value = result.get.value;
xml_kind = XML_Message_Kind_RESP_GET;
} break;
case Result_Kind_PUT: {
xml_kind = XML_Message_Kind_RESP_PUT_DELETE;
} break;
case Result_Kind_DEL: {
xml_kind = XML_Message_Kind_RESP_PUT_DELETE;
} break;
case Result_Kind_ERROR: {
error = result.error.error;
xml_kind = XML_Message_Kind_RESP_ERROR;
} break;
default: {
} break;
}
Char *output = xmlCreateMessage(xml_kind, key, value, error);
Size output_len = strlen(output);
int nsent = write(result.fd, output, output_len);
if (nsent == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
Output_Resume *or = calloc(1, sizeof(*or));
or->fd = result.fd;
or->output = output;
or->output_pos = 0;
or->output_len = output_len;
hmInsertI(&output_map, result.fd, (Uptr)or);
struct epoll_event event = {.data.fd = result.fd,
.events = EPOLLIN | EPOLLOUT};
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, result.fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_MOD");
exit(-1);
}
} else {
perror("write() failed");
exit(-1);
}
} else if ((Size)nsent < output_len) {
Output_Resume *or = calloc(1, sizeof(*or));
or->fd = result.fd;
or->output = output;
or->output_pos = nsent;
or->output_len = output_len;
hmInsertI(&output_map, result.fd, (Uptr)or);
struct epoll_event event = {.data.fd = result.fd,
.events = EPOLLIN | EPOLLOUT};
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, result.fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_MOD");
exit(-1);
}
} else {
struct epoll_event event = {.data.fd = result.fd,
.events = EPOLLIN};
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD,
result.fd, &event) < 0) {
perror("epoll_ctl EPOLL_CTL_MOD");
exit(-1);
}
sbufDelete(output);
hmRemoveI(&busy_map, result.fd);
}
switch (result.kind) {
case Result_Kind_GET: {
free(result.get.key);
free(result.get.value);
} break;
case Result_Kind_PUT: {
} break;
case Result_Kind_DEL: {
} break;
case Result_Kind_ERROR: {
free(result.error.error);
} break;
default: {
} break;
}
}
}
return 0;
}
internal_function
B32 storageMakeDirectoryIfNotExist (Char *path, Char **error)
{
struct stat st = {0};
if(stat(path, &st) == -1) {
if(mkdir(path, 0777) < 0) {
*error = strdup("IO Error: Can not create data container");
return false;
}
}
return true;
}
internal_function
B32 storageSetFileData(U64 hash, Char *key, Char *value, Char **error)
{
Char byte_one_two[5] = {0};
Char byte_three_four[5] = {0};
Char byte_five_six[5] = {0};
Char byte_seven_eight[5] = {0};
Char path[300] = {0};
FILE *file = NULL;
snprintf(byte_one_two, 5, "%04hx", (U16)((hash & 0xFFFF000000000000) >> 48));
snprintf(byte_three_four, 5, "%04hx", (U16)((hash & 0xFFFF00000000) >> 32));
snprintf(byte_five_six, 5, "%04hx", (U16)((hash & 0xFFFF0000) >> 16));
snprintf(byte_seven_eight, 5, "%04hx",(U16)(hash & 0xFFFF));
/* printf("SET: %s %s %s %s\n\t%s\n\t\t%s\n", */
/* byte_one_two, byte_three_four, byte_five_six, byte_seven_eight, */
/* key, value); */
strcat(path, byte_one_two);
strcat(path, "/");
if (!storageMakeDirectoryIfNotExist(path, error)) goto error;
strcat(path, byte_three_four);
strcat(path, "/");
if (!storageMakeDirectoryIfNotExist(path, error)) goto error;
strcat(path, byte_five_six);
strcat(path, "/");
if (!storageMakeDirectoryIfNotExist(path, error)) goto error;
strcat(path, byte_seven_eight);
strcat(path, "/");
if (!storageMakeDirectoryIfNotExist(path, error)) goto error;
strcat(path, key);
if((file = fopen(path, "w")) != NULL) {
fprintf(file, "%s", value);
fclose(file);
} else {
*error = strdup("IO Error: Cannot store data");
goto error;
}
cacheSetKV(hash, key, value);
return true;
error:
return false;
}
internal_function
Char* storageGetFileData(U64 hash, Char *key, Char **error)
{
Char *cached_value = cacheGetKV(hash, key, NULL);
if (cached_value != NULL) {
return strdup(cached_value);
}
Char byte_one_two[5] = {0};
Char byte_three_four[5] = {0};
Char byte_five_six[5] = {0};
Char byte_seven_eight[5] = {0};
Char path[300] = {0};
Char *value = calloc((KiB(256) + 1), sizeof(*value));
FILE *file = NULL;
snprintf(byte_one_two, 5, "%04hx", (U16)((hash & 0xFFFF000000000000) >> 48));
snprintf(byte_three_four, 5, "%04hx", (U16)((hash & 0xFFFF00000000) >> 32));
snprintf(byte_five_six, 5, "%04hx", (U16)((hash & 0xFFFF0000) >> 16));
snprintf(byte_seven_eight, 5, "%04hx",(U16)(hash & 0xFFFF));
/* printf("GET: %s %s %s %s\n\t%s\n", */
/* byte_one_two, byte_three_four, byte_five_six, byte_seven_eight, */
/* key); */
strcat(path, byte_one_two);
strcat(path, "/");
strcat(path, byte_three_four);
strcat(path, "/");
strcat(path, byte_five_six);
strcat(path, "/");
strcat(path, byte_seven_eight);
strcat(path, "/");
strcat(path, key);
if(access(path, F_OK) == 0) {
if((file = fopen(path, "r")) != NULL) {
fseek(file, 0L, SEEK_END);
Size size = ftell(file);
fseek(file, 0L, SEEK_SET);
fread(value, size, sizeof(Char), file);
fclose(file);
} else {
*error = strdup("IO Error: Cannot retrieve data");
goto error;
}
} else {
*error = strdup("Does not exist");
goto error;
}
return value;
error:
free(value);
return NULL;
}
/* internal_function */
/* void storageConvertPathToParent (Char *str) */
/* { */
/* Size s = strlen(str); */
/* while ((str[s] != '/') && (s != 0)) { */
/* str[s] = '\0'; */
/* s--; */
/* } */
/* str[s] = '\0'; */
/* if (s == 0) { */
/* str[0] = '.'; */
/* } */
/* return; */
/* } */
/* internal_function */
/* B32 storageDeleteEmptyDirectoryAndSwitchToParent (Char *path) */
/* { */
/* B32 result = false; */
/* struct dirent *d; */
/* int n = 0; */
/* DIR *dir = opendir(path); */
/* if (dir != NULL) { */
/* while ((d = readdir(dir)) != NULL) { */
/* if(++n > 2) { */
/* break; */
/* } */
/* } */
/* closedir(dir); */
/* if (n <= 2) { // Directory Empty */
/* rmdir(path); */
/* result = true; */
/* } */
/* } */
/* storageConvertPathToParent(path); */
/* return result; */
/* } */
internal_function
B32 storageDeleteFileData(U64 hash, Char *key, Char **error)
{
Char byte_one_two[5] = {0};
Char byte_three_four[5] = {0};
Char byte_five_six[5] = {0};
Char byte_seven_eight[5] = {0};
Char path[300] = {0};
snprintf(byte_one_two, 5, "%04hx", (U16)((hash & 0xFFFF000000000000) >> 48));
snprintf(byte_three_four, 5, "%04hx", (U16)((hash & 0xFFFF00000000) >> 32));
snprintf(byte_five_six, 5, "%04hx", (U16)((hash & 0xFFFF0000) >> 16));
snprintf(byte_seven_eight, 5, "%04hx",(U16)(hash & 0xFFFF));
/* printf("DEL: %s %s %s %s\n\t%s\n", */
/* byte_one_two, byte_three_four, byte_five_six, byte_seven_eight, */
/* key); */
strcat(path, byte_one_two);
strcat(path, "/");
strcat(path, byte_three_four);
strcat(path, "/");
strcat(path, byte_five_six);
strcat(path, "/");
strcat(path, byte_seven_eight);
strcat(path, "/");
strcat(path, key);
// FIXME(namna): This is in a race condition with the SetData
int ret = 0;
if(access(path, F_OK) == 0) {
if((ret = remove(path)) != 0) {
*error = strdup("IO Error: Cannot delete data");
goto error;
}
} else {
*error = strdup("Does not exist");
goto error;
}
/* storageConvertPathToParent(path); // Remove file name */
/* if (storageDeleteEmptyDirectoryAndSwitchToParent(path)) { */
/* if (storageDeleteEmptyDirectoryAndSwitchToParent(path)) { */
/* if (storageDeleteEmptyDirectoryAndSwitchToParent(path)) { */
/* storageDeleteEmptyDirectoryAndSwitchToParent(path); */
/* } */
/* } */
/* } */
Size cache_slot = 0;
Char *cached_value = cacheGetKV(hash, key, &cache_slot);
if (cached_value != NULL) {
cacheInvalidateSlot(cacheGetBucket(hash), cache_slot);
}
return true;
error:
return false;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment